目录
线程不安全原因:
多个线程对同一个数据进行操作。
多个线程对同一个数据进行操作可能会因为争夺CPU使用次数不同,导致执行顺序不同,从而产生结果与预期不一致
public static void main(String[] args) {
MyPiao myPiao = new MyPiao();
MyPiao myPiao2 = new MyPiao();
MyPiao myPiao3 = new MyPiao();
Thread thread = new Thread(myPiao,"窗口1");
Thread thread2 = new Thread(myPiao2,"窗口2");
Thread thread3 = new Thread(myPiao3,"窗口3");
thread.start();
thread2.start();
thread3.start();
}
}
class MyPiao implements Runnable{
private static int num=1000;
public MyPiao() {
super();
// TODO Auto-generated constructor stub
}
public void run() {
// TODO Auto-generated method stub
while (num>0) {
Thread thread = Thread.currentThread();
String nameString =thread.getName();
num--;
System.out.println(nameString+"卖了第"+num+"张");
}
}
例如:此代码线程进入while循环中时,num--,getName,打印,有可能这次线程争到了执行权,而下次没执行到,又有一个线程一直争取到了,导致打印结果会出现重复的
解决方法
使多个线程只对一个数据进行操作
解决方案
1.同步代码块
语法: synchronized (锁对象) {
要同步的代码
}
上锁:当一个线程进入synchronizend代码块中,其锁对象就会上锁,此时别的线程无法进入,只能在外边等待
开锁:当一个线程执行完synchronizend代码块中的代码,此锁对象就会开锁
注意:所有对象都可以作为锁对象
要保证多个线程的锁对象是同一个对象
private static final Object Lock=new Object();
Object是所有对象的父类,可以选用它创建的对象作为锁
有了以上工具可以对买票进行操作
先创建Object类的对象
private static final Object Lock=new Object();
再用其上锁
while (num>0) {
synchronized (Lock) {
if(num<=0) {//防止最后执行的两个线程不知道num<0了
return;//num用static修饰,是唯一的,当有窗口执行到0张时,
}
Thread thread=Thread.currentThread();
String nameString =thread.getName();
num--;//防止再进入到此处执行num--
System.out.println(nameString+"卖了第"+num+"张");
}
}
此时不会再重复
2.同步方法
语法:访问权限修饰符 synchronized 返回值类型 方法名(形参列表){
方法体(也就是需要同步的代码块)
}
注意:同步方法的锁对象,是调用该方法的对象(同步方法锁对象即是this)
public void run() {
sell();
}
public synchronized void sell() {//同步方法
while (num>0) {
if(num<=0) {//防止最后的两个线程不知道num<0了
return;
}
Thread thread=Thread.currentThread();
String nameString =thread.getName();
num--;
System.out.println(nameString+"卖了第"+num+"张");
}
此方法也可完成任务
3.同步静态方法
语法:
访问权限修饰符 synchronizat static 返回值类型 方法名(形参列表){
方法体(要同步的代码)
}
同步静态方法的锁对象是该方法所属的类的对象
类对象:
一个类被JVM加载时会产生一个类对象,该对象包含该类的所有信息,如该类的包名,类名,父类名,实现 的接口名,属性数量,属性名,方法数量,方法名等信息 因为一个类只会被加载一次,所以一个类只有一个类对象
死锁
死锁产生的原因是多个线程互相持有对方所需的锁资源
为避免尽量不要在同步中使用同步
public class Test {
public static void main(String[] args) {
final Object Lock1 = new Object();
final Object Lock2 = new Object();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (Lock1) {
System.out.println("thread1的锁");
synchronized (Lock2) {
System.out.println("thread2的锁");
}
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (Lock2) {
System.out.println("thread2的锁");
synchronized (Lock1) {
System.out.println("thread1的锁");
}
}
}
});
thread1.start();
thread2.start();
}
}
可以看到线程1,线程2,互相要对方的锁,产生了死锁。对于这个例子来说,只需改锁的顺序就可解决
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (Lock1) {
System.out.println("thread1的锁");
synchronized (Lock2) {
System.out.println("thread2的锁");
}
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (Lock1) {
System.out.println("thread2的锁");
synchronized (Lock2) {
System.out.println("thread1的锁");
}
}
}
});
改变锁的顺序
线程间通讯
1.方法由Object类提供
2.只能在同步代码块中使用,原因: 防止唤醒在睡眠之前,让通讯顺序与自己所想一样
3.必须使用该同步代码块的锁对象或同步方法的锁对象调用
1.休眠
锁对象.wait();
作用:让当前线程陷入无限期休眠
锁对象.wait(timeout);
作用:让当前线程有限期休眠,参数为休眠时间,单位:毫秒 ms
锁对象.wait(timeout, nanos);
作用:让当前线程有限期休眠,休眠时间毫秒+纳秒
参数1休眠时间,单位毫秒ms
参数2休眠时间,单位纳秒ns
wait与sleep的区别:
wait:
由Object类提供的普通方法
只能在同步中使用
休眠期间会释放持有锁对象
sleep:
Threak类提供的静态方法
可在同步中使用,也可在同步外使用
休眠期间不会释放所有的锁对象
sleep方法是当前线程休眠,让出cpu,不释放锁,这是Thread的静态方法;wait方法是当前线程等待,释放锁,这是Object的方法。
2.唤醒
锁对象.notify();
作用:随机唤醒一个使用 调用该方法的对象 作为 锁对象 的线程
锁对象.notifyAll();
作用:唤醒所有 使用该方法的对象 作为 锁对象 的线程
练习:龟兔赛跑,龟跑100米,1m/s,兔80米睡觉,10m/s,龟到终点叫兔子
public class Test1 {
public static final Object cokek= new Object();
public static void main(String[] args) {
Thread Tu = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <101; i=i+10) {
System.out.println("兔跑了"+i+"米");
if(i==80) {
System.out.println("睡了");
synchronized (cokek) {//锁内使用
try {
cokek.wait();//休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
System.out.println("兔完了");
}
});
Thread Gui = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <101; i++) {
System.out.println("龟跑了"+i+"米");
}
System.out.println("乌龟已完");
synchronized (cokek) {//锁内使用
cokek.notify();//龟唤醒所有进程
}
}
});
Gui.start();
Tu.start();
}
}
练习:
1.使用两个线程 1个线程打印1~52 1个线程打印a~z
要求:打印输出结果为12a34b56c78d910e1112f....5152z
原理:是1进程停止,让2进程打开,2运行,2停止,让1打开,1停止,如此循环。
所以如果某次抢夺CPU失败,有可能运行失败,等会 2题 试试新方法
package work7;
public class Name implements Runnable {
@Override
public void run() {
for (int i = 97; i < 123; i++) {
try {
Thread.sleep(10);//保证数字先打,字母睡一会
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.print((char)i);
synchronized (Colek.LOCK) {
Colek.LOCK.notify();//唤醒对方,因为只有一个,所以随机必定是对方
try {
Colek.LOCK.wait();//自己睡了,等对面唤醒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
package work7;
public class Zeioz implements Runnable {
@Override
public void run() {
for (int i = 1; i < 52; i=i+2) {
System.out.print(i);//打印数字
System.out.print(i+1);//打印这个数字后面的数字
synchronized (Colek.LOCK) {
Colek.LOCK.notify();//唤醒对方,因为只有一个,所以随机必定是对方
try {
Colek.LOCK.wait();//自己睡了,等对面唤醒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
package work7;
public class Test {
public static void main(String[] args) {
Zeioz zeioz = new Zeioz();
Name name = new Name();
Thread a = new Thread(zeioz);
Thread b = new Thread(name);
a.start();
b.start();
}
}
3.生产者,消费者模式整一个
package work9;
public class Gongc {//工厂
public static int nam=0;//设定初始商品
public static final int Mix=1001;//仓库上限1001个
public synchronized void Shengc() {//生产方法
Thread thread=Thread.currentThread();//获取线程名字有用
if (nam<Mix) {//商品小于仓库就生产
nam++;//生产
System.out.println(thread.getName()+"生产的,还有"+nam+"个");
this.notifyAll();//主要让销售启动
}else {//大于的话满了
System.out.println("仓库已满"+thread.getName()+"停止生产");
try {
this.wait();//停下生产
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void Chus() {//销售方法
Thread thread=Thread.currentThread();
if(nam>0) {//大于0有货
nam--;//销售
System.out.println(thread.getName()+"销售的,还有"+nam+"个");
this.notifyAll();//主要生产启动下
}
else {
System.out.println(thread.getName()+"停止销售,已经销售完");
try {
this.wait();//没了,停止销售
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package work9;
public class Xiaos implements Runnable {//销售
private Gongc gongc;//
public Xiaos() {
super();
}
public Xiaos( Gongc gongc) {
super();
this.gongc = gongc;
}
@Override
public void run() {
while (true) {//007别停下来
gongc.Chus();//出售方法
}
}
}
package work9;
public class Shengc implements Runnable{//生产
private Gongc gongc;
public Shengc() {
super();
}
public Shengc(Gongc gongc) {
super();
this.gongc = gongc;
}
@Override
public void run() {
while (true) {//007别停
gongc.Shengc();//生产方法
}
}
}
package work9;
public interface Suo {
public static final Object Lock=new Object();//Object整个锁
}
package work9;
public class Test {
public static void main(String[] args) {
Gongc gongc = new Gongc();
Xiaos xiaos = new Xiaos(gongc);
Shengc shengc = new Shengc(gongc);
Thread thread = new Thread(shengc,"小王");
Thread thread2 = new Thread(shengc,"小贺");
Thread thread3 = new Thread(shengc,"小高");
Thread thread4 = new Thread(shengc,"老八");
Thread thread5 = new Thread(xiaos,"小莉");
Thread thread6 = new Thread(xiaos,"翠翠");
Thread thread7 = new Thread(xiaos,"小花");
thread.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();
thread6.start();
thread7.start();
}
}