线程与同步
什么是同步
同步就是加锁,不让其它人访问
synchronized指的就是同步的意思
什么情况下需要同步
当多线程并发, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步,否则会有线程安全问题.
同步代码块
1 使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块
2 多个同步代码块如果使用相同的锁对象, 那么他们就是同步的
3 使用同步锁时,应该尽是让锁的范围小点,才能提高性能
同步方法
1 使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的
2 非静态同步方法的锁是:this
3 静态同步方法的锁是:字节码对象(xx.class)
例子:火车站售票
public class 锁 {
public static void main(String[] args) {
// 生成任务
Task task = new Task();
Thread a = new Thread(task);
a.setName("a售票口");
Thread b = new Thread(task);
b.setName("b售票口");
Thread c = new Thread(task);
c.setName("c售票口");
Thread d = new Thread(task);
d.setName("d售票口");
// 开始线程 多线程是抢占式的
a.start();
b.start();
c.start();
d.start();
}
}
//买票任务
class Task implements Runnable {
int ticket = 100;
// 使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的
// 非静态同步方法的锁是:this
// 静态同步方法的锁是:字节码对象(xx.class)
@Override
public synchronized void run() {
// 同步代码块,锁最好同一个对象,如果不是同一对象,还是会有线程安全问题
// * 锁:this,代表当前对象
// * 锁:如果 new 对象,就不是同一把锁
// * 锁:字节码对象例如:String.class,内存中,只有一个字节码对象
// synchronized (this) {
while (true) {
if (ticket <= 0) {
System.out.println(Thread.currentThread() + "票卖完了");
break;
} else {
System.out.println(Thread.currentThread() + "抢到票了 票号:" + ticket);
ticket--;
}
}
}
// }
}
执行效果
锁的总结
同步代码块,锁最好同一个对象,如果不是同一对象,还是会有线程安全问题
- 锁:this,代表当前对象
- 锁:如果 new 对象,就不是同一把锁
- 锁:字节码对象 例如:String.class,内存中,只有一个字节码对象
- 如果锁对象是唯一,线程执行时就会独占该锁,一直到锁包含的代码块执行完才将锁释放,此后被阻塞的线程方能获得该锁,从而重新进入可执行状态。
死锁
多线程以及多进程改善了系统资源的利用率并提高了系统 的处理能力。然而,并发执行也带来了新的问题——死锁。所谓死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。
简单来说:死锁就是大家都抱着锁,不释放锁
一个简单的锁案例
package 线程与同步;
public class 死锁 {
public static void main(String[] args) {
String s1 = "筷子a";
String s2 = "筷子b";
new Thread() {
public void run() {
while (true) {
//s1对象锁
synchronized (s1) {
System.out.println("线程a拿到了" + s1 + " 需要" + s2);
//s2对象锁
synchronized (s2) {
System.out.println("线程a拿到了" + s2 + " 开始吃饭");
}
}
}
};
}.start();
new Thread() {
public void run() {
while (true) {
//s2对象锁
synchronized (s2) {
System.out.println("线程b拿到了" + s2 + " 需要" + s1);
//s1对象锁
synchronized (s1) {
System.out.println("线程b拿到了" + s1 + " 开始吃饭");
}
}
}
};
}.start();
}
}
效果图
原因:
当a线程占用着s1对象锁 需要s2对象锁执行后面代码时,b线程占用着s2对象锁 需要s1对象锁执行后面代码。两个线程各自占有对方想要的资源 不肯释放就造成了死锁。
回顾线程安全的类
Vector,StringBuffer,Hashtable
Vector是线程安全的,ArrayList是线程不安全的 (具体可以看源码)
StringBuffer是线程安全的,StringBuilder是线程不安全的 (具体可以看源码)
Hashtable是线程安全的,HashMap是线程不安全的(具体可以看源码)
单例设计模式
什么是单例
保证类在内存中只有一个对象。
对象是new出来的,因些也就是说在程序中只能new一次对象
单例实现的基本步骤
1 声明一个类,类中有一个静态属性,类型与类名相同
2 把空参构造方法声明为私有
3 在类中提供一个公共静态访问方法来返回该对象实例
单例的多种写法
//一饿汉式
//class Singleton {
//
// private static Singleton singleton = new Singleton();
//
// private Singleton() {
//
// }
//
// public static Singleton getSingleton() {
// return singleton;
// }
//}
//二懒汉式
//class Singleton {
//
// private static Singleton singleton;
//
// private Singleton() {
//
// }
//
// public static Singleton getSingleton() {
// if (singleton == null)
// singleton = new Singleton();
// return singleton;
// }
//}
//三简单方式
class Singleton {
public final static Singleton singleton = new Singleton();
private Singleton() {
}
}
饿汉式和懒汉式的区别
饿汉式是空间换时间,懒汉式是时间换空间
在多线程访问时,饿汉式不会创建多个对象,而懒汉式有可能会创建多个对象
如果考虑线程安全问题,用饿汉式
如果不考虑线程安全问题,用懒汉式
Runtime类的使用
Runtime类是一个单例类
每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。通过 getRuntime 方法获取当前运行时。
案例:自动关机
Runtime r = Runtime.getRuntime();
r.exec(“shutdown -s -t 300”);//300秒后关机
r.exec(“shutdown -a”); //取消关机
Timer定时器
Timer一种工具,用于在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。
方法
public void schedule(TimerTask task, long delay)
public void schedule(TimerTask task, long delay, long period)
public void schedule(TimerTask task, Date firstTime, long period)
public class 定时器 {
public static void main(String[] args) {
Timer timer=new Timer();
//内部会根据new TimerTask() 创建子线程
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread()+"执行了任务A");
}
}, 3000);
}
}
ps: Thread[Timer-0,5,main]执行了任务A
{ Timer:线程名
5:优先级
main:该线程所在线程组}