1、线程安全的懒汉式
1.1、线程安全的懒汉式,代码如下:
//一个单一设计模式的类如下:
public class Bank {
private double account;
private String name;
private static Bank instance = null;
//私有化构造器
private Bank() {
}
private Bank(double account, String name) {
this.account = account;
this.name = name;
}
public void setAccount(double account) {
this.account = account;
}
public void setName(String name) {
this.name = name;
}
public double getAccount() {
return account;
}
public String getName() {
return name;
}
//这里就会出现线程安全的问题,我们可以利用同步方法来解决这个问题
public synchronized static Bank getInstance() {
if (instance == null) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
instance = new Bank();
}
return instance;
}
}
//测试类
public class Test {
private static Bank b1 = null;
private static Bank b2 = null;
public static void main(String[] args) {
Thread t1 = new Thread() {
@Override
public void run() {
b1 = Bank.getInstance();
}
};
Thread t2 = new Thread() {
@Override
public void run() {
b2 = Bank.getInstance();
}
};
t1.start();
t2.start();
//主线程在执行到下面的输出语句时分线程还没执行到,所以b1和b2都还没有被赋值,我们需要让分线程先执行!
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
System.out.println(b1);
System.out.println(b2);
System.out.println(b1 == b2);
}
}
输出结果:
Test02.Bank@41629346 Test02.Bank@41629346 true
这里有一种优化方案,就是当一个线程创建好对象之后,后面的线程就可以不用再去执行同步代码块里面的内容,而是直接拿着对象走,相当于去店里排队买东西,当东西已经卖完的时候,后面的人就不需要再接着去排队,而是直接走人。
优化后的代码如下所示:
public class Bank {
private double account;
private String name;
private static Bank instance = null;
//私有化构造器
private Bank() {
}
private Bank(double account, String name) {
this.account = account;
this.name = name;
}
public void setAccount(double account) {
this.account = account;
}
public void setName(String name) {
this.name = name;
}
public double getAccount() {
return account;
}
public String getName() {
return name;
}
//这里就会出现线程安全的问题,我们可以利用同步方法来解决这个问题
public static Bank getInstance() {
if (instance == null) { //如果这个对象不为空,那么就直接得到相应的对象,而不再去走同步代码块里面的内容
synchronized (Bank.class) {
if (instance == null) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
instance = new Bank();
}
}
}
return instance;
}
}
但是这里依旧会存在一个问题:指令重排,就是当一个线程进入同步代码块时,在创建对象的时候已经把对象创建好了,但是此时还没有执行创建对象的init方法,就是还没有进行初始化,那么后面的线程在进行if判断的时候就会直接跳过,从而获得一个不完整的对象,引发问题。
解决办法,给共享数据加上:volatile,如下所示:
private static volatile Bank instance = null;
2、死锁
2.1、死锁的定义:不同的线程分别占用对方需要的同步资源不放,都在等待对方放弃自己需要的同步资源,从而形成死锁。
死锁的例子:
public class Test {
public static void main(String[] args) {
StringBuilder s1 = new StringBuilder();
StringBuilder s2 = new StringBuilder();
new Thread() {
@Override
public void run() {
synchronized (s1) {
s1.append("a");
s2.append("1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
synchronized (s2) {
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread() {
@Override
public void run() {
synchronized (s2) {
s1.append("a");
s2.append("1");
synchronized (s1) {
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
}
}
2.2、诱发死锁的原因:
互斥条件
占用且等待
不可抢夺(或不可抢占)
循环等待
3、ReentrantLock的使用(在jdk5.0之后)
3.1、使用的步骤:
①、首先声明ReentrantLock的对象(注意要解决线程安全问题这里依旧要保证对象唯一!)
②、调用ReentrantLock对象里面的lock和unlock方法,将操控共享数据的代码放在lock和unlock之间
测试代码如下所示:
import java.util.concurrent.locks.ReentrantLock;
public class SellTicket implements Runnable {
private int ticket = 100; //票数
private Object object = new Object();
private static final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
try {
lock.lock();
if (this.ticket > 0) {
System.out.println(Thread.currentThread().getName() + "正在售票\t" + "票号为:" + this.ticket);
this.ticket--;
} else {
System.out.println("票已卖完!");
break;
}
} finally {
lock.unlock(); //为了让资源一定能得到释放
}
}
}
}
4、线程通信
4.1、为什么需要线程通信:当我们需要多个线程来共同完成同一件任务,并且我们希望它们有规律的执行,那么多线程之间需要一些通信机制,可以协调它们的工作,以此来实现多线程共同操作一份数据。
比如:线程A用来生产包子,线程B用来吃包子,包子可以理解为同一资源,线程A和线程B处理的动作,一个是生产,一个是消费,此时线程B必须等待A线程完成后才能执行,那么线程A和线程B之间就需要线程通信,即:==等待唤醒机制!==
案例引入:现在有一个需求,有两个线程,我们现在要将两个线程交替打印出1 - 100。
注意:wait()和sleep的一个区别:执行wait方法时会释放对同步监视器的占用,而sleep不会释放!
public class PrintNum implements Runnable{
private static final ReentrantLock lock = new ReentrantLock();
private int num = 1;
@Override
public void run() {
while(true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
synchronized (this){
this.notify(); //一旦执行此方法,就会唤醒被wait的优先级最高的线程,被唤醒的线程接着当初被wait的地方继续执行。如果有多个优先级相同的线程,那么就随机唤醒一个,如果要唤醒全部,可以用notifyAll(),其中这三个方法必须写在同步代码块或者同步方法中,不能使用在lock中。
if(num <=100){
System.out.println(Thread.currentThread().getName() + ":\t" + num);
num++;
try {
this.wait(); //一旦执行此方法,线程就进入等待状态,并且释放对同步监视器的占用
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}else {
break;
}
}
}
}
}
注意:
①、三个方法的调用者必须是同步监视器,否则会报异常!
②、其中这三个方法必须写在同步代码块或者同步方法中,不能使用在lock中。
5、生产消费问题
生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。
//销售作为公共资源
public class Clerk {
private int number = 0; //起始的数量
private static final int MAX = 20; //最大的数量
//生产产品(增加产品)
public synchronized void produceProduct() {
if(number >= Clerk.MAX){
System.out.println("数量达标,已等待生产!");
try {
wait();
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}else {
number++;
System.out.println(Thread.currentThread().getName() + "开始生成产品了!当前产品数量为:" + this.number);
notifyAll(); //只要生产了一个产品就唤醒消费者
}
}
//销售产品(减少产品)
public synchronized void buyProduct() {
if(number <= 0){
System.out.println("店中没有产品了,请消费者等一下");
try {
wait();
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}else {
number--;
System.out.println(Thread.currentThread().getName() + "消费了产品!当前产品数量为:" + this.number);
notify(); //只要消费了产品就唤醒生产者
}
}
}
//顾客
public class Customer implements Runnable {
private Clerk clerk;
public Customer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
clerk.buyProduct();
}
}
}
//生产者
public class Producer implements Runnable {
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
clerk.produceProduct();
}
}
}
6、创建线程的其它两种方式
6.1、实现Callable接口,使用如下所示:
import java.util.concurrent.Callable;
public class MySum implements Callable {
@Override
public Object call() throws Exception { //前面的返回值类型是一个泛型
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
}
//测试类
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test {
public static void main(String[] args) {
MySum mySum = new MySum();
FutureTask futureTask = new FutureTask(mySum);
Thread t1 = new Thread(futureTask);
t1.start();
//获取返回值
try {
Object sum = futureTask.get();
System.out.println("100以内的总数为:" + sum);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
} catch (ExecutionException e) {
System.out.println(e.getMessage());
}
}
}
该方式的好处:有了返回值,可以让线程的执行变得更加的灵活。==并且它在和主线程同时执行的时候,必须要等它有了返回值之后主线程才能拿到返回值,而不会出现它还没有返回主线程就获得的情况。==
6.2、线程池
如下所示:
定义两个实现Runnable接口的线程操作类如下所示:
public class PrintNum1 implements Runnable{
@Override
public void run() {
System.out.println("20以内的偶数如下:");
for (int i = 1; i <= 20; i++) {
if(i % 2 == 0){
System.out.println(i);
}
}
}
}
public class PrintNum2 implements Runnable{
@Override
public void run() {
System.out.println("20以内的奇数如下:");
for (int i = 1; i <= 20; i++) {
if(i % 2 != 0){
System.out.println(i);
}
}
}
}
测试类:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
public class Test {
public static void main(String[] args) {
//提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//类型转换
ThreadPoolExecutor service1 = (ThreadPoolExecutor)service;
//设置线程池的线程数量的上限
service1.setMaximumPoolSize(20);
//执行指定的线程操作,线程操作需要实现Runnable接口
service.execute(new PrintNum1());
service.execute(new PrintNum2());
// 适用于实现Callbale接口的:service.submit(Callable callable);
//关闭连接池
service.shutdown();
}
}
标签:private,线程,println,多线程,public,Bank,out
From: https://blog.51cto.com/u_15433911/7024135