一、内存可见性
如果一个线程对共享变量的修改,能够被其它线程看到,那么就能说明共享变量在线程之间是可见的。如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量。
线程对共享变量的所有操作都必须在自己的工作内存中进行,不能从主内存中读写;而且不同线程之间无法直接访问其它线程工作内存中的变量,线程间变量值的传递需要通过主内存来完成,所以不加任何修饰的,线程之间内存是不可见的。
二、volatile 关键字
详细介绍看此处 万能青年,公众号:JavaArtisanJUC 多线程之 volatile 关键字
三、原子变量
因为volatile关键字不能保证原子性操作,所以需要java.util.concurrent.atomic包下的原子变量来进行修改,底层使用CAS算法保证数据的原子性。
四、CAS算法
五、同步容器类ConcurrentHashMap
在map集合中,HashMap效率高,但是不能保证线程安全,HashTable可以保证线程安全,但是效率非常低,在需要保证线程安全的情况下,也不使用HashTable,因为效率实在是太低,所以使用ConcurrentHashMap。
在完成某些运算时,只有当所有线程的运算全都完成后,才继续运行的这种情况下,需要用到闭锁。
七、创建线程的四种方式
1、继承Thread类
class MyThread extends Thread {
@Override
public void run() {
System.out.println("继承Thread类");
}
}
public class Test {
public static void main(String args[]) {
//启动继承Thread类创建的线程对象
MyThread t1 = new MyThread();
t1.start();
}
2、实现Runnable接口
class RunnableDemo implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++){
System.out.println(i);
}
}
}
public class TestRunable {
public static void main(String[] args) {
RunnableDemo runnableDemo = new RunnableDemo();
new Thread(runnableDemo).start();
}
}
3、实现Callable接口
public class TestCallable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadDemo threadDemo = new ThreadDemo();
//执行callable方式,需要FuturnTask实现类的支持,用于接收运算结果
FutureTask<Integer> futureTask = new FutureTask<>(threadDemo);
//启动线程
new Thread(futureTask).start();
//接收运算结果
Integer sum = futureTask.get();
System.out.println(sum);
}
}
class ThreadDemo implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++){
sum += i;
}
return sum;
}
}
4、线程池创建线程
八、同步锁的使用
同步锁是一个显示锁,需要lock()方法上锁,unlock()释放锁
public class TestLock {
public static void main(String[] args) {
LockDemo lockDemo = new LockDemo();
new Thread(lockDemo, "1号").start();
new Thread(lockDemo, "2号").start();
new Thread(lockDemo, "3号").start();
}
}
class LockDemo implements Runnable{
private int ticket = 100;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
//上锁
lock.lock();
try {
if (ticket > 0){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "完成售票,余票为" + --ticket);
}
}finally {
//释放锁
lock.unlock();
}
}
}
}
九、生产者消费者问题解决
package com.artisan.juc;
/**
* @author wannengqingnian
*/
public class ProAndCus {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Productor productor = new Productor(clerk);
Consumer consumer = new Consumer(clerk);
new Thread(productor, "生产者A1").start();
new Thread(consumer, "消费者B1").start();
new Thread(productor, "生产者A2").start();
new Thread(consumer, "消费者B2").start();
}
}
/**
* 店员
*/
class Clerk{
private int product = 0;
public synchronized void get() {
while (product >= 144){
System.out.println("产品已满!");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//进货
System.out.println(Thread.currentThread().getName() + " : " + ++product);
this.notifyAll();
}
public synchronized void sales(){
while (product <= 0){
System.out.println("产品缺货!");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//卖货
System.out.println(Thread.currentThread().getName() + " : " + --product);
this.notifyAll();
}
}
/**
* 生产者
*/
class Productor implements Runnable{
private Clerk clerk;
public Productor(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++){
clerk.get();
}
}
}
/**
* 消费者
*/
class Consumer implements Runnable{
private Clerk clerk;
public Consumer(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++){
clerk.sales();
}
}
}
十、使用Lock完成生产者消费者问题
package com.artisan.juc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author wannengqingnian
*/
public class ProAndCus {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Productor productor = new Productor(clerk);
Consumer consumer = new Consumer(clerk);
new Thread(productor, "生产者A1").start();
new Thread(consumer, "消费者B1").start();
new Thread(productor, "生产者A2").start();
new Thread(consumer, "消费者B2").start();
}
}
/**
* 店员
*/
class Clerk{
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private int product = 0;
public void get() {
lock.lock();
try {
while (product >= 1){
System.out.println("产品已满!");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//进货
System.out.println(Thread.currentThread().getName() + " : " + ++product);
condition.signalAll();
}finally {
lock.unlock();
}
}
public void sales(){
lock.lock();
try {
while (product <= 0){
System.out.println("产品缺货!");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//卖货
System.out.println(Thread.currentThread().getName() + " : " + --product);
condition.signalAll();
}finally {
lock.unlock();
}
}
}
/**
* 生产者
*/
class Productor implements Runnable{
private Clerk clerk;
public Productor(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++){
clerk.get();
}
}
}
/**
* 消费者
*/
class Consumer implements Runnable{
private Clerk clerk;
public Consumer(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++){
clerk.sales();
}
}
}
十一、读写锁
读读不互斥,读写互斥,写写互斥,而一般的独占锁是:读读互斥,读写互斥,写写互斥,而场景中往往读远远大于写,读写锁就是为了这种优化而创建出来的一种机制。
Synchronized存在明显的一个性能问题就是读与读之间互斥,读写锁可以解决这个问题。
代码演示:
package com.artisan.juc;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 读写锁
*
* 创建一个写线程
* 20个读线程
* @author wannengqingnian
*/
public class TestReadWriteLock {
public static void main(String[] args) {
ReadWriteLockDemo readWriteLockDemo = new ReadWriteLockDemo();
new Thread(new Runnable() {
@Override
public void run() {
readWriteLockDemo.set((int) (Math.random() * 101));
}
}, "写线程").start();
for (int i = 1; i <= 20; i++){
new Thread(new Runnable() {
@Override
public void run() {
readWriteLockDemo.get();
}
}, "读线程 " + i).start();
}
}
}
class ReadWriteLockDemo {
private int number = 0;
/**
* 创建读写锁
*/
private ReadWriteLock lock = new ReentrantReadWriteLock();
public void get(){
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " : " + number);
}finally {
lock.readLock().unlock();
}
}
public void set(int number){
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName());
this.number = number;
}finally {
lock.writeLock().unlock();
}
}
}
十二、线程八锁
代码演示:
/*
* 题目:判断打印的 "one" or "two" ?
*
* 1. 两个普通同步方法,两个线程,标准打印, 打印? //one two
* 2. 新增 Thread.sleep() 给 getOne() ,打印? //one two
* 3. 新增普通方法 getThree() , 打印? //three one two
* 4. 两个普通同步方法,两个 Number 对象,打印? //two one
* 5. 修改 getOne() 为静态同步方法,打印? //two one
* 6. 修改两个方法均为静态同步方法,一个 Number 对象? //one two
* 7. 一个静态同步方法,一个非静态同步方法,两个 Number 对象? //two one
* 8. 两个静态同步方法,两个 Number 对象? //one two
*
* 线程八锁的关键:
* ①非静态方法的锁默认为 this, 静态方法的锁为 对应的 Class 实例
* ②某一个时刻内,只能有一个线程持有锁,无论几个方法。
*/
public class TestThread8Monitor {
public static void main(String[] args) {
Number number = new Number();
Number number2 = new Number();
new Thread(new Runnable() {
@Override
public void run() {
number.getOne();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// number.getTwo();
number2.getTwo();
}
}).start();
/*new Thread(new Runnable() {
@Override
public void run() {
number.getThree();
}
}).start();*/
}
}
class Number{
public static synchronized void getOne(){//Number.class
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
System.out.println("one");
}
public synchronized void getTwo(){//this
System.out.println("two");
}
public void getThree(){
System.out.println("three");
}
}
总结:
1、一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,其他的线程都只能等待,换句话说,某一时刻内,只能有唯一一个线程去访问这些synchronized方法。
2、所有的非静态同步方法用的都是同一把锁 -- 实例对象本身,也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁,可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁,所以毋须等待该实例对象已经取锁的非静态同步方法释放锁就可以获取他们自己的锁。
3、所有的静态同步方法用的也是同一把锁 -- 类对象本身,这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间不会有竞争条件。但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们是同一个实例对象。
原文链接:https://blog.csdn.net/kfengqingyangk/article/details/69485858
十三、线程池
需要并发线程数量很多,可以避免新创建与销毁线程产生额外的开销,提高响应速度,使线程可以复用。线程池提供了一个线程队列,队列中保存着所有等待状态的线程。
Java.util.concurrent.Executor : 负责线程的使用与调度的根接口
|-- ExecutorService 子接口:线程池的主要接口
|--ThreadPoolExecutor :线程池的实现类
|--ScheduledExecutorService 子接口 :负责线程的调度
|--ScheduledThreadPoolExecutor : 继承ThreadPoolExecutor,实现ScheduledExecutorService,具备两者的功能
ExecutorService new FixedThreadPool() : 创建固定大小的线程池。
ExecutorService newCachedThreadPool() : 缓存线程池,线程池的线程数量不固定,可以根据需求自动更改数量。
ExecutorService newSingleThreadPool() : 创建单个线程的线程池。
ScheduledExecutorService newScheduledThreadPool() : 创建固定大小的线程池,可以延时或定时执行任务。
4、使用线程池
runnable方法:
package com.artisan.juc;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
/**
* @author wannengqingnian
*/
public class TestThreadPool {
public static void main(String[] args) {
TestRunnableDemo demo = new TestRunnableDemo();
//1.创建线程池
ExecutorService pool = Executors.newFixedThreadPool(5);
//2.为线程池中的线程分配任务
for (int i = 0; i < 10; i++){
pool.submit(demo);
}
//3.关闭线程
pool.shutdown();
}
}
class TestRunnableDemo implements Runnable{
private int i = 0;
@Override
public void run() {
while (i <= 100){
System.out.println(Thread.currentThread().getName() + " : " + i++);
}
}
}
Callable方式:
package com.artisan.juc;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
/**
* @author wannengqingnian
*/
public class TestThreadPool {
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallableDemo cdemo = new TestCallableDemo();
List < Future < Integer > > futures = new ArrayList<>();
//1.创建线程池
ExecutorService pool = Executors.newFixedThreadPool(5);
//2.为线程池中的线程分配任务 Callable方式
for (int i = 0 ; i < 10; i++){
Future<Integer> future = pool.submit(cdemo);
futures.add(future);
}
for (Future future : futures){
System.out.println(future.get());
}
//3.关闭线程
pool.shutdown();
}
}
class TestCallableDemo implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++){
sum += i;
}
return sum;
}
}
十四、面试题
package com.artisan.juc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 三个线程,三个线程ID分别是ABC,打印20遍,显示结果按顺序显示
* 如 ABCABC...
* @author wannengqingnian
*/
public class PrintingInSequence {
public static void main(String[] args) {
PrintDemo printDemo = new PrintDemo();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 20; i++){
printDemo.loopA(i);
}
}
}, "A").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 20; i++){
printDemo.loopB(i);
}
}
}, "B").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 20; i++){
printDemo.loopC(i);
}
}
}, "C").start();
}
}
class PrintDemo{
private int number = 1;
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void loopA(int total){
lock.lock();
try {
//1.判断是不是到该自己执行,不到则等待
if (number != 1){
condition1.await();
}
//2.打印自己编号
for (int i = 1 ; i <= 1; i++){
System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + total);
}
//3.自己结束后,唤醒下一个线程
number = 2;
condition2.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void loopB(int total){
lock.lock();
try {
//1.判断是不是到该自己执行,不到则等待
if (number != 2){
condition2.await();
}
//2.打印自己编号
for (int i = 1 ; i <= 1; i++){
System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + total);
}
//3.自己结束后,唤醒下一个线程
number = 3;
condition3.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void loopC(int total){
lock.lock();
try {
//1.判断是不是到该自己执行,不到则等待
if (number != 3){
condition3.await();
}
//2.打印自己编号
for (int i = 1 ; i <= 1; i++){
System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + total);
System.out.println("==========================");
}
//3.自己结束后,唤醒下一个线程
number = 1;
condition1.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}