一 、多线程实现的前两种常见方式
第一种 继承Thread
步骤:
1.新建线程类继承Thread
2.重写里面的run方法
3.新建线程类对象
4.调用start()方法
public class TheadsTest {
public static void main(String[] args) {
Fri fri = new Fri();
fri.start(); //主次参杂运行
//调用start方法开启子线程
for (int i=0; i<500; i++){ //在main方法就是主线程
System.out.println("main thread----"+i);
}
}
}
class Fri extends Thread{
@Override
public void run() {
for (int i=0; i<500; i++){
System.out.println("分线程1 ---"+i);
}
}
}
第二种 实现Runnable接口
步骤:
- 新建任务类实现runnable接口
- 新建线程类
- 线程的构造方法中传入runnable接口
- 启动线程,调用start方法
// :Runnable接口的实现类对象,表示一个具体的任务,将来创建一个线程对象之后,让线程执行这个任务
public class TheadsTest {
public static void main(String[] args) {
Fri fri = new Fri();
Thread thread = new Thread(fri); //新建线程将实现类对象传入 方法一没有这一句
thread.start(); //以线程类开启子线程
for (int i=0; i<500; i++){
System.out.println("main thread----"+i);
}
}
}
class Fri implements Runnable{
@Override
public void run() {
for (int i=0; i<500; i++){
System.out.println("分线程1 ---"+i);
}
}
}
匿名内部类创建方式
//使用匿名内部类创建线程
Thread thread2 = new Thread() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("0Zi Thread------"+i);
}
}
};
thread2.start();
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 50; i++) {
System.out.println("1Zi Thread------"+i);
}
}
});
thread3.start();
//主方法放到最前面则最先优先主方法,一般都是主方法执行完子进程才平级竞争
for (int i = 0; i < 50; i++) {
System.out.println("Main Thread======"+i);
}
常用方法
//常用方法
thread.getName(); //获取线程的名称
thread.setName("name"); //设置线程的名称 线程开始之前在构造方法中也可以设置 开始之后也可以
Thread t2Thread = new Thread("thread name");
Thread.currentThread(); //获取当前正在执行这段代码的线程对象
//Example
Thread t4 = new Thread() {
@Override
public void run() {
Thread currentThread = Thread.currentThread();
for (int i = 0; i < 50; i++) {
System.out.println(currentThread.getName()+"--"+i);
}
}
};
yield(); //主动释放当前线程的执行权
join() //在线程中插入执行另一个线程,该线程被阻塞,直到插入执行的线程完全执行完毕以后,该线程才继续执行下去
stop() //(已过时) 强制结束当前线程
isAlive() //判断当前线程是否存活
// thread.sleep(millis); //线程休眠,单位毫秒 时间到了自动苏醒
// 有一个异常,中断异常:InterruptedException
// 在普通方法中,可以声明
// 在run方法中,必须只能处理,不能声明
垃圾回收线程
//垃圾回收线程的名称
new GC();
System.gc(); //名称为 finalize
//获取主方法所在线程名称 在主方法内部任意位置写
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName());
守护线程
守护线程就是用于守护其他线程可以正常运行的线程,在为其他的核心线程准备良好的运行环境。
如果非守护线程全部死亡,守护线程就没有存在的意义了,一段时间之后,虚拟机也一同结束
//守护线程
thread.setDaemon(true); //建立了线程之后调用这个方法,将值设置为true即可
线程优先级
通过给定优先级数字设定优先级,数字越大,优先级越高
数字范围:最小1,最大的是10,默认状态就是5
//设置优先级
thread.setPriority(Thread.MAX_PRIORITY);
thread2.setPriority(Thread.MIN_PRIORITY);
thread3.setPriority(Thread.NORM_PRIORITY); //也可以直接写入优先级1-10
线程调度问题
调度策略:
时间片:线程的调度采用时间片轮转
抢占式:高优先级的线程抢占CPU
Java的调度方法:
1.对于同优先级的线程组成先进先出队列,使用时间片策略
2.对高优先级,使用优先调度的抢占式策略
线程中的线程安全问题
某段代码在没有执行完成的时候,cpu就可能被其他线程抢走,结果导致当前代码中的一些数据发生错误
原因:没有保证某段代码的执行的完整性、原子性
希望:这段代码要么全都执行,要么全都没有执行
//同步代码块
//format:↓
synchronized (currentThread) { //可以写入一个字符串或者是当前对象
//中间写入需要同步的代码块 需要保证完整性的代码
}
当cpu想去执行同步代码块的时候,需要先获取到锁对象,获取之后就可以运行代码块中的内容;
当cpu正在执行当前代码块的内容时,cpu可以切换到其他代码,但是不能切换到具有相同锁对象的代码上。
当cpu执行完当前代码块中的代码之后,就会释放锁对象,cpu就可以运行其他具有当前锁对象的同步代码块了
其他方式
修饰符 synchronized 返回值类型 方法名(参数列表){
}
如果是非静态的方法,同步方法的锁对象就是this,当前对象,哪个对象调用这个同步方法,这个同步方法使用的锁就是哪个对象
如果是静态的方法,同步方法的锁对象就是当前类的字节码对象,类名.class(在方法区的一个对象)
哪个类在调用这个同步方法,这个同步方法使用的锁就是哪个类的字节码对象
总结:
1.同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
2.非静态的同步方法,同步监视器是this
静态的同步方法,同步监视器是当前类本身。继承自Thread.class
JDK5.0新增的lock锁方法
import java.util.concurrent.locks.ReentrantLock;
class Window implements Runnable{
private int ticket = 100;//定义一百张票
//1.实例化锁
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
//2.调用锁定方法lock
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售出第" + ticket + "张票");
ticket--;
} else {
break;
}
}
}
}
public class LockTest {
public static void main(String[] args){
Window w= new Window();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口1");
t3.setName("窗口1");
t1.start();
t2.start();
t3.start();
}
}
参考代码 原文链接:https://blog.csdn.net/weixin_44797490/article/details/91006241
synchronized与lock异同
相同:二者都可以解决线程安全问题
不同:synchronized机制在执行完相应的代码逻辑以后,自动的释放同步监视器
lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())(意味着lock的方式更为灵活)
优先使用顺序:
LOCK -> 同步代码块 ->同步方法
判断是否存在线程安全问题
首先看是否是多线程(即是否能够按照一条线执行完全部,能则为单个线程)
数据是否共享
是否并发操作共享数据
例题
/***
* 描述:甲乙同时往银行存钱,存够3000
*
*
* */
//账户
class Account{
private double balance;//余额
//构造器
public Account(double balance) {
this.balance = balance;
}
//存钱方法
public synchronized void deposit(double amt){
if(amt>0){
balance +=amt;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"存钱成功,余额为:"+balance);
}
}
}
//两个顾客线程
class Customer extends Thread{
private Account acct;
public Customer(Account acct){
this.acct = acct;
}
@Override
public void run() {
for (int i = 0;i<3;i++){
acct.deposit(1000);
}
}
}
//主方法,之中new同一个账户,甲乙两个存钱线程。
public class AccountTest {
public static void main(String[] args){
Account acct = new Account(0);
Customer c1 = new Customer(acct);
Customer c2 = new Customer(acct);
c1.setName("甲");
c2.setName("乙");
c1.start();
c2.start();
}
}
//参考文章 原文链接:https://blog.csdn.net/weixin_44797490/article/details/91006241
解决线程安全问题的方式
单例:只能通过静态方法获取一个实例,不能通过构造方法来构造实例
假设有多个线程调用此单例,而调用的获取单例的函数作为操作共享单例的代码块并没有解决线程的安全问题,会导致多个线程都判断实例是否为空,此时就会导致多个实例的产生,也就是单例模式的线程安全问题。
解决线程安全问题的思路:
将获取单例的方法改写成同部方法,即加上synchronized关键字,此时同步监视器为当前类本身。(当有多个线程并发的获取实例时,同时只能有一个线程获取实例),解决了单例模式的线程安全问题。
用同步监视器包裹住同步代码块的方式。
懒汉式单例模式的模型,例如:生活中的限量版的抢购:
当一群人并发的抢一个限量版的东西的时候,可能同时抢到了几个人,他们同时进入了房间(同步代码块内)
但是只有第一个拿到限量版东西的人才能到手,其余人都不能拿到,所以效率稍高的做法是,当东西被拿走时,我们在门外立一块牌子,售罄。
这样就减少了线程的等待。即下面效率稍高的懒汉式写法:
public class Bank {
//私有化构造器
private Bank(){}
//初始化静态实例化对象
private static Bank instance = null;
//获取单例实例,此种懒汉式单例模式存在线程不安全问题(从并发考虑)
public static Bank getInstance(){
if(instance==null){
instance = new Bank();
}
return instance;
}
//同步方法模式的线程安全
public static synchronized Bank getInstance1(){
if(instance==null){
instance = new Bank();
}
return instance;
}
//同步代码块模式的线程安全(上锁)
public static Bank getInstance2(){
synchronized (Bank.class){
if(instance==null){
instance = new Bank();
}
return instance;
}
}
//效率更高的线程安全的懒汉式单例模式
/**
* 由于当高并发调用单例模式的时候,类似于万人夺宝,只有第一个进入房间的人才能拿到宝物,
* 当多个人进入这个房间时,第一个人拿走了宝物,也就另外几个人需要在同步代码块外等候,
* 剩下的人只需要看到门口售罄的牌子即已知宝物已经被夺,可以不用进入同步代码块内,提高了效率。
*
*
* */
public static Bank getInstance3(){
if (instance==null){
synchronized (Bank.class){
if(instance==null){
instance = new Bank();
}
}
}
return instance;
}
}
死锁
有了同步代码块的嵌套,就可能发生死锁。某条线程获取了外层的锁对象A,需要内层的锁对象B,等待;另外一条线程获取了外层的锁对象B,需要内层的锁对象A,等待。两条线程就会形成死锁。
// main中
Thread thread4 = new Thread() {
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
synchronized ("A zy") {
System.out.println("get A,wait for B zy");
synchronized ("B zy") {
System.out.println("get AB zy ,start!");
}
}
}
}
};
Thread thread5 = new Thread() {
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
synchronized ("B zy") {
System.out.println("get B,wait for A zy");
synchronized ("A zy") {
System.out.println("get AB zy,start!");
}
}
}
}
};
thread4.start();
thread5.start();
解决思路
减少锁的嵌套
减少共享变量
使用针对性的算法,规定线程先后调用顺序
线程案例
火车票案例,多个窗口开启售票
class Ticket extends Thread{
static int number = 100; //车票张数
@Override
public void run() {
while (true) { //循环要放到外面 每一次循环结束都会重新让多个子进程重竞争
synchronized ("1") { //保证一次只有一个窗口可以完成代码块
if (number<0) {
break;
}
try {
sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName()+" sale "+number+" ticket.");
number--;
}
}
}
}
在main方法中调用测试如下
//火车票问题
Ticket t1 = new Ticket();
t1.setName("window1");
Ticket t2 = new Ticket();
t2.setName("window2");
Ticket t3 = new Ticket();
t3.setName("window3");
t1.start();
t2.start();
t3.start();
公交车问题
class Bus extends Thread{ //T1
static int seat = 80; //全局变量
@Override
public void run() {
while (true) {
synchronized ("2") { //锁的名称可以使用字符串
//由于循环条件while 在外面 每一次循环都会发生资源的重新选择
if (seat<=0) { //得到资源的线程开始进入下一次循环
break;
}
try {
sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName()+" entry the "+seat+" person.");
seat--;
}
}
}
}
main方法测试如下
Bus b1 = new Bus();
b1.setName("F door");
Bus b2 = new Bus();
b2.setName("M door");
Bus b3 = new Bus();
b3.setName("E door");
b2.start();
b1.start();
b3.start();
二、 后两种较特殊的线程实现方式
第三种 callable方式(拥有返回值)
runnable重写的run方法不如callaalbe的call方法强大,call方法可以有返回值
可以获取返回结果 但需要FutureTask类
方法可以抛出异常,支持带有泛型的返回值
/callable实现新建线程的步骤:
1.创建一个实现callable的类
2.实现call方法
3.创建callable实现类的对象
4.将上面的对象放到FutureTask的构造方法中,创建一个FutureTask对象
5.将FutureTask的对象作为参数放入Thread类的构造方法中,创建Thread对象,并调用start方法启动
通过FutureTask的对象,调用get方法获取call的返回值
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class TheadsTest {
public static void main(String[] args) {
Fri fri = new Fri(); // 创建该对象
FutureTask futureTask = new FutureTask(fri); // 传入futureTask构造
Thread thread = new Thread(futureTask); // 准备开启线程 构造线程
thread.setName("thread1--");
thread.start();
try {
// 获取返回值
Object o = futureTask.get();
System.out.println(Thread.currentThread().getName()+"result "+o);
System.out.println("main thread " + o+"---main result");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class Fri implements Callable {
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 0; i<10; i++){
System.out.println(i);
sum += i;
}
return sum;
}
}
第四种 线程池方式
背景:经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用。(类比数据库连接池)
好处:
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理
线程池参数:
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
JDK 5.0 起提供了线程池相关API:
ExecutorService 和 Executors
ExecutorService:真正的线程池接口。
常见子类ThreadPoolExecutor.
void execute(Runnable coommand):执行任务/命令,没有返回值,一般用来执行Runnable
Futuresubmit(Callable task):执行任务,有返回值,一般又来执行Callable
void shutdown():关闭连接池。
不同类型的线程池
Executors | 工具类,线程池的工厂类,用于创建并返回不同类型的线程池 |
---|---|
Executors.newCachedThreadPool() | 创建一个可根据需要创建新线程的线程池 |
Executors.newFixedThreadPool(n) | 创建一个可重用固定线程数的线程池 |
Executors.newSingleThreadExecutor() | 创建一个只有一个线程的线程池 |
Executors.newScheduledThreadPool(n) | 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。 |
import java.util.concurrent.*;
public class TheadsTest {
public static void main(String[] args) {
// 创建线程
Fri fri = new Fri();
Sec sec = new Sec();
thr thr = new thr();
// 定义有10个固定线程的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
// 执行线程最多10个
executorService.execute(fri);
executorService.execute(sec); // execute方式用于runnable
executorService.submit(thr); // 适用于callable
executorService.shutdown(); //关闭
}
}
class Fri implements Runnable {
@Override
public void run() {
for (int i=0 ;i<50; i++){
System.out.println("thread fri---" +i);
}
}
}
class Sec implements Runnable{
@Override
public void run() {
for (int i=0 ;i<50; i++){
System.out.println("thread sec---" +i);
}
}
}
class thr implements Callable{
@Override
public Object call() throws Exception {
int i = 10;
return i;
}
}
线程通信
定义在Object中的三个方法
wait()
notify()
notifyAll()
这三个方法均需要用到锁,而任意对象都可以作为锁的对象,故而定义在Object中
wait()
进入被锁的区域后进入等待,阻塞状态,释放锁并让别的线程进来先操作,在原地等待别的线程的通知
notify()
进入到被锁住的区域进行操作,并唤醒等待中的线程
注意
两个方法均需要在被锁的区域中使用,即使用了synchronized的方法或者代码块,且为了表示 wait 和 notify 的是同一个锁区域,就需要锁来标记,也就是锁必须是同一个对象
线程通信应用案例
/**
* 线程通信的应用:生产者/消费者问题
*
* 1.是否是多线程问题?是的,有生产者线程和消费者线程(多线程的创建,四种方式)
* 2.多线程问题是否存在共享数据? 存在共享数据----产品(同步方法,同步代码块,lock锁)
* 3.多线程是否存在线程安全问题? 存在----都对共享数据产品进行了操作。(三种方法)
* 4.是否存在线程间的通信,是,如果生产多了到20时,需要通知停止生产(wait)。(线程之间的通信问题,需要wait,notify等)
*
* */
class Clerk{
private int productCount = 0;
//生产产品
public synchronized void produceProduct() {
if(productCount<20) {
productCount++;
System.out.println(Thread.currentThread().getName()+":开始生产第"+productCount+"个产品");
notify();
}else{
//当有20个时,等待wait
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//消费产品
public synchronized void consumeProduct() {
if (productCount>0){
System.out.println(Thread.currentThread().getName()+":开始消费第"+productCount+"个产品");
productCount--;
notify();
}else{
//当0个时等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread{//生产者线程
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+";开始生产产品......");
while(true){
clerk.produceProduct();
}
}
}
class Consumer implements Runnable{//消费者线程
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":开始消费产品");
while(true){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumeProduct();
}
}
}
public class ProductTest {
public static void main(String[] args){
Clerk clerk = new Clerk();
Producer p1 = new Producer(clerk);
p1.setName("生产者1");
Consumer c1 = new Consumer(clerk);
Thread t1 = new Thread(c1);
t1.setName("消费者1");
p1.start();
t1.start();
}
}
参考文章
原文链接:https://blog.csdn.net/weixin_44797490/article/details/91006241