第一种方法:同步代码块
代码示例:
/**
* 运行出现 打印出0,-1,-2错票
* 线程出现了安全问题
*
* 问题原因:当多条语句操作‘同一个’线程共享数据时,一个线程对多条语句只执行了一部分,还没有
* 执行完,另一个线程参与进来执行。导致共享数据的错误。
*
* 解决办法:
* 对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行
*
* java对于多线程的安全问题提供了专业的解决方式
* 就是同步代码块。
*
* synchronized(对象)
* {
* 需要同步的代码
* }
*
* 对象如同锁。持有锁的线程可以在同步中执行
* 没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁
*
* 火车的卫生间 -- 经典
*
* 同步的前提
* 1,必须有两个或者两个以上的线程
* 2,必须是多个线程使用同一个锁
*
* 必须保证同步中只能有一个线程在运行
*
* 好处:解决了多线程的安全问题
*
* 弊端:多个线程都需要判断锁,较为消耗资源。
*
*/
public class TicketDemo implements Runnable{
private int tick = 100;
Object obj = new Object();
public void run() {
while (true) {
synchronized (obj){
if (tick > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "sale..." + tick--);
}
}
}
}
public static void main(String[] args) {
TicketDemo demo = new TicketDemo();
Thread t1 = new Thread(demo);
Thread t2 = new Thread(demo);
Thread t3 = new Thread(demo);
Thread t4 = new Thread(demo);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
第二种方式:同步函数
代码示例:
/**
* 需求:
* 银行有一个金库
* 有两个储户分别存300元,每次存100
*
* 目的:该程序是否有安全问题,如果有,如何解决?
*
* 如何找问题?
* 1,明确哪些代码是多线程运行代码
* 2,明确共享数据
* 3,明确多线程运行代码中哪些语句是操作共享数据的
*/
public class Bank {
private int sum;
// Object obj = new Object();
public synchronized void add(int n) {
// synchronized (obj){
sum += n;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sum = " + sum );
// }
}
}
class Cus implements Runnable{
private Bank bank = new Bank();
public void run() {
for (int i = 0; i < 3; i++) {
bank.add(100);
}
}
}
class BankDemo{
public static void main(String[] args) {
Cus cus = new Cus();
Thread t1 = new Thread(cus);
Thread t2 = new Thread(cus);
t1.start();
t2.start();
}
}
同步代码块中的锁是this
/**
* 同步函数用的是哪一个锁?
* 函数需要被对象调用。那么函数都有一个所属对象的引用。就是this
* 所以同步函数使用的锁是this
*
* 通过改程序进行验证:
* 使用两个线程来买票
* 一个在同步代码块中
* 一个在同步函数中
* 都在执行买票动作
*/
public class Ticket implements Runnable{
private int tick = 100;
Object obj = new Object();
boolean flag = true;
public void run() {
if(flag){
while (true){
synchronized (this){
if (tick > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "sale..." + tick--);
}
}
}
}else {
while (true)
this.show();
}
}
public synchronized void show() {
if (tick > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "sale..." + tick--);
}
}
}
class TicketDemo{
public static void main(String[] args) throws InterruptedException {
Ticket demo = new Ticket();
Thread t1 = new Thread(demo);
Thread t2 = new Thread(demo);
t1.start();
Thread.sleep(10);
demo.flag = false;
t2.start();
}
}
静态方法使用的锁是什么?
/**
* 如果同步函数被静态修饰后,使用的锁是什么?
*
* 通过验证发现不是this。因为静态方法中不可以定义this
*
* 静态进内存时,内存中没有本类对象,但一定有该类对应的二进制码文件对象。
* 类名.class,该对象类型是class
*
* 静态的同步方法使用的锁是该方法所在类的字节码文件对象 类名.class
*/
public class Ticket implements Runnable{
private static int tick = 100;
Object obj = new Object();
boolean flag = true;
public void run() {
if(flag){
while (true){
synchronized (Ticket.class){
if (tick > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "sale..." + tick--);
}
}
}
}else {
while (true)
this.show();
}
}
public static synchronized void show() {
if (tick > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "sale..." + tick--);
}
}
}
class TicketDemo{
public static void main(String[] args) throws InterruptedException {
Ticket demo = new Ticket();
Thread t1 = new Thread(demo);
Thread t2 = new Thread(demo);
t1.start();
Thread.sleep(10);
demo.flag = false;
t2.start();
}
}
/**
* 解决懒汉模式的多线程安全问题
* 双重判断可解决同步代码低效问题
*/
public class Single
{
private static Single single;
private Single()
{
}
public static Single getInstance()
{
if (single == null)
{
synchronized (Single.class)
{
if (single == null)
{
single = new Single();
}
}
}
return single;
}
}