目录直通车
一、同步代码块
sysnchronized(同步监视器){
// 需要被同步的代码块(即为共享数据的代码)
}
1) 共享数据:多个线程共同操作的同一个数据(变量)
2) 同步监视器:有一个类的对象来充当。哪个线程获取此监视器,谁就去执行大括号里被同步的代码。俗称:锁。要求所有的线程共用同一把锁。
下面提供一个用同步代码块的方式解决数据共享的安全问题:
实例
class W implements Runnable{
int ticket = 100;
// Object obj = new Object();
public void run(){
while(true) {
/*
在继承中一定要慎用this
严格控制:要求所有的线程共用同一把锁,
也就是说这个this代表同一个对象的时候才能够使用。
*/
synchronized (this) {
if (ticket > 0) {
try{
Thread.currentThread().sleep(100);
System.out.println(Thread.currentThread().getName() + "售票,余票为:" + ticket--);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
}
}
public class TestThreadSecurity {
public static void main(String[] args){
W window = new W();
Thread window1 = new Thread(window);
Thread window2 = new Thread(window);
Thread window3 = new Thread(window);
window1.setName("窗口1");
window2.setName("窗口2");
window3.setName("窗口3");
window1.start();
window2.start();
window3.start();
}
}
二、 同步方法
将操作共享数据的方法声明为sysnchronized。即此方法,能够保证其中一个线程执行此方法时,其它线程在外等待直至此线程执行完此方法。
同步方法的锁:this。
class Win implements Runnable{
int ticket = 100;
// Object obj = new Object();
public void run(){
while(true){
show();
}
}
public synchronized void show(){
if (ticket > 0 ){
try{
Thread.currentThread().sleep(10);
System.out.println(Thread.currentThread().getName()+"售票,余票为:"+ticket--);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
public class TestThreadSecurity2 {
public static void main(String[] args){
Win window = new Win();
Thread window1 = new Thread(window);
Thread window2 = new Thread(window);
Thread window3 = new Thread(window);
window1.setName("窗口1");
window2.setName("窗口2");
window3.setName("窗口3");
window1.start();
window2.start();
window3.start();
}
}
这里还涉及到一个互斥锁的概念。
先了解一下单例模式:https://blog.csdn.net/qq_41647999/article/details/83447936
单例模式一般用于比较大、复杂的对象,只初始化一次,应该还有一个private的构造函数,使得不能用new来实例化对象,只能调用getInstance方法来得到对象,而getInstance保证了每次调用都返回相同的对象。
这里在举一个实例,解释在代码里:
class Singleton{
private Singleton(){
}
/*
这里存在一个问题, 两个线程(A、B)分别调用getInstance方法,
首次调用的时候,instance肯定为null,A在创建Singleton之前被挂起,
线程B进入之后也被挂起,当前的instance仍然为null。此时A继续执行
创建一个Singleton对象,假设此时在内存中的地址为1111,线程B执行之后
也会创建一个对象,大家肯定都知道在内存中的值一定不为1111,
像这样返回出去的判断相等不相等的时候就有问题了。为什么呢?因为在这里的instance
只能够new一次,这种情况却new了两次。
*/
private static Singleton instance = null;
/*
这里为什么要判断一次instance为null?
假设这里只有一家华为手机店(这里的代码块),店里面只有一台mate20,
门口排了一大堆的人(线程)来买mate20,第一个人肯定买的到,后面的人
就买不到了,但是没有人告诉后面的人卖完了,每个人还得进店一次。所以
这里的instance不为null的时候,就是告诉后面的线程,说这里的mate20
已经卖完了,不用排队了。
*/
if (instance == null ){
public static Singleton getInstance(){
if (instance == null ){
instance = new Singleton();
}
return instance;
}
}
}
public class TestSingleton {
public static void main(String[] args) {
/*
getInstance在单例模式(保证一个类仅有一个实例,
并提供一个访问它的全局访问点)的类中常见,
用来生成唯一的实例,getInstance一般是static的
*/
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);
}
}
上例BUG修复
class Singleton2{
private Singleton2(){
}
private static Singleton2 instance = null;
public static Singleton2 getInstance() {
/*
1、 static里面不能使用this
2、 关于解决懒汉式的线程安全问题,需要使用同步机制
3、 对于静态方法而言,使用当前类本身充当锁
*/
synchronized (Singleton2.class) {
/*
关于这里为什么使用Singleton2.class为什么能行,涉及到反射的知识点。
*/
if (instance == null) {
instance = new Singleton2();
}
}
return instance;
}
}
public class TestSingleton2 {
public static void main(String[] args) {
Singleton2 s1 = Singleton2.getInstance();
Singleton2 s2 = Singleton2.getInstance();
System.out.println(s1 == s2);
}
}