eg:
public class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" 正在运行...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 结束...");
}
}
public class ThreadDemo {
public static void main(String[] args) {
MyThread my =new MyThread();
Thread t1 = new Thread(my,"小白");
Thread t2 = new Thread(my,"凤凤");
t1.start();
t2.start();
}
}
运行输出:
凤凤 正在运行...
小白 正在运行...
小白 结束...
凤凤 结束...
从以上可以看到,凤凤正在运行的时候小白也开始运行了,从而有可能会发生对同一资源的争抢。理想的状态应该是 凤凤运行的时候小白便不能再运行,需要等到凤凤运行结束后才能开始运行。
解决数据共享问题,必须使用同步,所谓同步就是指多个线程在同一个时间段内只能有一个线程执行指定代码,其他线程要等待次线程完成之后才可以继续执行
线程进行同步,有以下两种方法:
(1)、同步代码块
synchronized(要同步的对象){
要同步的操作;
}
(2)、同步方法
public synchronized void method(){
要同步的操作
}
eg1:
public class MyThread implements Runnable{
Object object = new Object();//同步标记对象,任意一个
@Override
public void run() {
//同步代码块
synchronized (object) {
System.out.println(Thread.currentThread().getName()+" 正在运行...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 结束...");
}
}
}
/**
* 多线程共享数据的安全问题,使用同步解决
* 1、同步代码块
*/
public class ThreadDemo {
public static void main(String[] args) {
MyThread my =new MyThread();
Thread t1 = new Thread(my,"小白");
Thread t2 = new Thread(my,"凤凤");
t1.start();
t2.start();
}
}
运行结果:
凤凤 正在运行...
凤凤 结束...
小白 正在运行...
以上的object仅是作为标记,任意对象都可以
eg2:
public class MyThread implements Runnable{
@Override
public void run() {
doMethod();
}
//同步方法, 当前对象(this)作为标记对象
public synchronized void doMethod(){
System.out.println(Thread.currentThread().getName()+" 正在运行...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 结束...");
}
}
ThreadDemo 同 eg1
输出结果:
小白 正在运行...
小白 结束...
凤凤 正在运行...
凤凤 结束...
同步代码会带来性能降低的问题,牺牲性能提高数据的安全性
当编写synchronized块时,有几个简单的准则可以遵循,这些准则在避免死锁和性能危险的风险大有帮助:
(1) 使代码块保持简短,把不随线程变化的预处理和后处理移出synchronized块
(2) 不要阻塞。如 InputStream.read()
(3) 在持有锁的时候,不要对其他对象调用方法
过多的同步有可能出现死锁,死锁的操作一般是程序运行的时候才有可能出现
多线程中要进行资源的共享,就需要同步,但同步过多,就可能造成死锁
eg:
//顾客
class Customer{
public synchronized void say(Waiter w){
System.out.println("顾客:说先服务,再付费");
w.doService();
}
public synchronized void doService(){
System.out.println("同意先付款再享受服务");
}
}
class Waiter{
public synchronized void say(Customer c){
System.out.println("服务员说:先付费,再服务");
c.doService();
}
public synchronized void doService(){
System.out.println("同意先服务再收费");
}
}
//死锁线程
class DeadThread implements Runnable{
Customer c =new Customer();
Waiter w = new Waiter();
public DeadThread(){
new Thread(this).start();
w.say(c);
}
@Override
public void run() {
c.say(w);
}
}
public class DeadThreadDemo {
public static void main(String[] args) {
new DeadThread();
}
}
运行:
有可能会出现线程死锁
输出:
服务员说:先付费,再服务
顾客:说先服务,再付费
正常:
服务员说:先付费,再服务
同意先付款再享受服务
顾客:说先服务,再付费
同意先服务再收费
死锁原因: (3) 在持有锁的时候,不要对其他对象调用方法
new Thread(this).start();//执行会调用 c.say(w);
w.say(c);
如果 c.say(w); 先执行,对象c会被上锁,并会调用 w.doService ,由于该方法加了同步锁,因而 对象 w会被上锁,所以当 w.say(c); 调用的时候,由于对象 w被上锁,故而等待,后面运行正常。
如果两者同时执行,c.say(w)先锁住 c ,w.say(c) 先锁住 w ,因而 对象c 无法使用 say(w)中的w,对象w无法使用say(c)中的c对象,从而产生死锁
因而死锁的产生是有一定几率的。