- 前言
- 线程的创建方式
- synchronized
- 使用场景
- 在何处加
- 1. 不加synchronized
- 2. 修饰方法
- 3. 修饰this
- 4. 方法内修饰变量
- 5. synchronized修饰静态
- 总结
一个进程内可以存在多个线程,一个线程也可以存在多个协程。
在一般的开发环境中,会经常利用到线程。
- extends Thread
直接继承Thread类,重写run()方法 - implements Runnable
最常用的创建方式,实现Runnable接口,重写run()方法 - implements Callable
有返回值,重写run()方法
推荐使用implements的方法创建线程
synchronized 使用场景- 直接在创建的线程类中加关键字synchronized
- 新建一个公有类,可作为一项服务,多个线程对该服务进行访问。在这个公有类中加关键字synchronized
我们在这里定义一个场景:一个线程可以不停的抢票,直到票数为0
1. 不加synchronizedclass MyThread implements Runnable{
//定义100张票
private int tickets = 100;
//打印抢票信息
public void print(){
System.out.println(Thread.currentThread().getName()+"抢到了第"+this.tickets+"张票");
}
@Override
public void run() {
//这里的run方法未添加synchronized
while(this.tickets > 0){
this.print();
this.tickets--;
}
}
}
public class RunnableDemo {
public static void main(String[] args) {
MyThread myThread1 = new MyThread();
new Thread(myThread1,"A").start();
new Thread(myThread1,"B").start();
new Thread(myThread1,"C").start();
new Thread(myThread1,"D").start();
new Thread(myThread1,"E").start();
}
}
部分运行结果
不使用synchronized同步,会出现数据的脏读
class MyThread implements Runnable{
private int tickets = 100;
public void print(){
System.out.println(Thread.currentThread().getName()+"抢到了第"+this.tickets+"张票");
}
@Override
public synchronized void run() {
//用synchronized修饰
while(this.tickets > 0){
this.print();
this.tickets--;
}
}
}
public class RunnableDemo {
public static void main(String[] args) {
MyThread myThread1 = new MyThread();
new Thread(myThread1,"A").start();
new Thread(myThread1,"B").start();
new Thread(myThread1,"C").start();
new Thread(myThread1,"D").start();
new Thread(myThread1,"E").start();
}
}
部分运行结果
使用synchronized同步,数据按照我们所想的方向运行
在此部分,只改动run方法的代码
@Override
public void run() {
synchronized (this){
while(this.tickets > 0){
this.print();
this.tickets--;
}
}
}
输出结果和2部分相同,仍是同步的
this 表示的是当前对象的引用,当前对象是谁?线程1、2都实现了同一个方法,自然他们的对象是相同的,this指向的对象都是同一个对象,自然会线程安全、同步输出。
那如果我加一行代码呢?
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"是非synchronized修饰的方法内的输出");
synchronized (this){
while(this.tickets > 0){
this.print();
this.tickets--;
}
}
}
看一下输出结果:
在《Java多线程编程核心技术》一书中,有这样一段话: “不在synchronized块中就是异步执行,在synchronized块中就会死同步执行”
上面的例子中,run()方法分为两部分,一部分是普通的输出print,另一部分是synchronized修饰的部分。
普通的输出print部分:异步执行。
synchronized修饰部分:同步执行
【抽象说】
线程A:我们start()了!!!冲!冲!冲!!!!!!!!!! (众人):冲!!!!run()!run()!run()! (众人):我输出了!我输出了!我输出了!我输出了!我输出了!... (由于线程B身强体壮,起跑快,冲到了第一个) 线程B:耽误太多时间,事情可就做不完了。抱歉了,兄弟们,先走一步!(说完便拿起锁) (此时的synchronized块,被线程B私有) (众人):卧槽!别啊!给我们锁啊!别让我们阻塞! 线程B:哇哦!AWESOME MAN!!鹅鹅鹅!这是我独享的MOMENT!!!!! (线程B抢完了所有的票)4. 方法内修饰变量
synchronized() 修饰的应是Object类
此时我们换一个稍微复杂点的场景
有两个人前来买瓜,有个人卖瓜 ; 瓜摊上有两个瓜:一个10斤、一个15斤 ; 单价2块钱 买瓜的:我 和 华强 我买10斤的,华强买15斤的 我花20块钱,华强花30块钱
- 变量定义在类中 (类中的全局变量)
class Melon{
private final int singlePrice = 2; //单价
private int weight; //重量
String words = Thread.currentThread().getName()+"说:挑个瓜,告诉我多少钱";
public void setWeight(int weight) {
try{
synchronized (words){
System.out.println(Thread.currentThread().getName()+"于"+System.currentTimeMillis()+"来了!"+words);
this.weight = weight ;
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() +"于"+System.currentTimeMillis()+ "买到了瓜,价格为"+this.weight * this.singlePrice);
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
//我的线程
class Me extends Thread{
Melon melon;
Me(Melon melon){
super();
this.melon = melon;
}
@Override
public void run() {
melon.setWeight(10);
}
}
//华强的线程
class HuaQiang extends Thread{
Melon melon;
HuaQiang(Melon melon){
super();
this.melon = melon;
}
@Override
public void run() {
melon.setWeight(15);
}
}
public class RunnableDemo {
public static void main(String[] args) {
Melon melon = new Melon();
Thread me = new Me(melon);
Thread huaqiang = new HuaQiang(melon);
me.setName("我");
huaqiang.setName("华强");
me.start();
huaqiang.start();
}
}
输出结果
可以看到时同步输出,排队
- 变量定义在方法中(方法内的局部变量)
将words变量放在方法内定义
public void setWeight(int weight) {
String words = Thread.currentThread().getName()+"说:挑个瓜,告诉我多少钱";
try{
synchronized (words){
System.out.println(Thread.currentThread().getName()+"于"+System.currentTimeMillis()+"来了!"+words);
this.weight = weight ;
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() +"于"+System.currentTimeMillis()+ "买到了瓜,价格为"+this.weight * this.singlePrice);
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
可以看到,此时两个线程同时到达函数体内部,是异步运行,而且注意到线程并不安全
【抽象说】
我和华强同时到了,准备了不同的话(String中的内容不同) 我抢先一句:老板,来个瓜 华强几乎同时说:给我挑一个 老板懵了,看到我们是两句话,使用了影分身。 老板1号给我的瓜称重,10斤,20块。 老板2号给华强的瓜称重,15斤,30块。(稍慢了0.00000001纳秒 ⚠️此时秤砣是一个,30块覆盖了20块。 老板1号对我说:30块。 老板2号对华强说:30块。 我:???5. synchronized修饰静态
无论是静态方法还是静态变量,在类的初始化中都会率先执行;同时也侧面说明了,静态方法/静态变量是和类绑定的。
同时也可以推断出,synchronized修饰静态,也同时将class类修饰。
class类是单例的,当用synchronized修饰静态时,针对相同类、不同对象、不同方法,仍会同步运行。
如果线程们访问的x是同一个,或者说x是同一个地址,则是同步,排队运行,保证线程安全;
如果线程们访问的x不是同一个,或者说x是不同的地址,则是异步,同时运行,不能保证线程安全。 3. 用static synchronized修饰时,将class类对象作为锁


![[基础/java]synchronized的基本思想 [基础/java]synchronized的基本思想](http://www.mshxw.com/aiimages/31/343741.png)
