提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
一、线程简介
多线程是指一个进程在执行过程中可以产生多个更小的程序单元,这些更小的单元称为线程,这些线程可以同时存在,同时运行,一个进程可 能包含多个同时执行的线程。
- 线程就是独立的执行路径
- 在程序运行时,及时没有自己创建线程,后台也会有很多线程
- main()成为主线程,为系统的入口,用于执行整个程序
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
- 线程会带来额外开销,如cpu调度时间、并发控制开销
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
二、线程创建
Thread类、Runnable接口
1、继承Thread类
- 自定义线程类继承Thread类
- 重写run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
public static void main(String[] args){
StartThread t = new StartThread();
t.start();
}
public static class StartThread extends Thread {
//线程入口点
public void run(){
//线程体
for(int i = 0 ; i < 20 ; i++)
System.out.println("我在听---");
}
}
2、实现Runnable接口
- 定义类实现Runnable接口
- 实现run()方法,编写线程执行体
- 创建线程对象,执行线程要丢入runnable接口的实现类,调用start()方法启动线程
public static void main(String[] args){
//创建runnable接口的实现类对象
TestThread testThread = new TestThread();
//创建线程对象,通过线程对象来开启线程
Thread thread = new Thread(testThread);
thread.start();
}
public static class TestThread implements Runnable{
public void run(){
for(int i = 0 ; i < 10 ;i++)
System.out.println("多线程");
}
}
三、线程状态
- 运行中(RUNNING):就绪的线程获得 CPU 的时间片就变为运行中。
- 阻塞(BLOCKED):线程等待获得锁
- 等待(WAITING):接收事件通知后或系统中断后进入等待
- 超时(TIMED_WAITING):等待指定时间后会自行返回
- 终止(TERMINATED):线程已执行完毕
线程方法:
- setPriority(int newPriority):更改线程的优先级
- static void sleep(long millis):在指定的毫秒数内让当前正在执行的线程休眠
- void join():等待该线程终止
- static void yield():暂停当前正在执行的线程对象,并执行其他线程
- void interrupt():中断线程
- boolean isAlive():测试线程是否处于活动状态
1、停止线程
用一个标志位进行终止变量,当flag=false的时候,则终止线程运行
package thread;
public class task03 {
public static void main(String[] args){
TestStop testStop = new TestStop();
new Thread(testStop).start();
for(int i = 0 ; i < 1000 ; i++){
if(i==900)
testStop.stop();
}
}
public static class TestStop implements Runnable{
//设置一个标志位
private boolean flag = true;
public void run(){
int i = 0;
while(flag){
System.out.println("run");
}
}
public void stop(){
this.flag = false;
}
}
}
2、线程休眠
- sleep(时间)指定当前线程阻塞的毫秒数
- sleep存在异常InterruptedException
- sleep时间达到后线程进入就绪状态
- sleep可以模拟网络延时、倒计时等
- 每一个对象都有一个锁,sleep不会释放锁
package thread;
public class task04 {
//模拟倒计时
public static void main(String[] args){
try{
tenDown();
}catch (InterruptedException e){
e.printStackTrace();
}
}
private static void tenDown() throws InterruptedException{
int num = 10;
while(true){
Thread.sleep(1000);
System.out.println(num--);
if(num<=0)
break;
}
}
}
3、线程礼让
- 礼让线程,让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转为就绪状态
- 让cpu重新调度,但礼让不一定成功
package thread;
public class task05 {
public static void main(String[] args){
MyYield myYield = new MyYield();
new Thread(myYield,"a").start();
new Thread(myYield,"b").start();
}
}
class MyYield implements Runnable {
public void run(){
System.out.println(Thread.currentThread().getName()+"线程开始执行");
Thread.yield();
System.out.println(Thread.currentThread().getName()+"线程停止执行");
}
}
4、线程强制执行
package thread;
public class test06 {
public static void main(String[] args) throws InterruptedException{
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
//主线程
for(int i = 0 ; i < 500 ;i++){
if(i==200){
thread.join();
}
System.out.println("main");
}
}
static class TestJoin implements Runnable{
public void run(){
for(int i = 0 ; i < 1000 ;i++)
System.out.println("插队");
}
}
}
5、线程优先级
package thread;
import com.company.Task10_01;
public class test07 {
public static void main(String[] args){
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread t1 = new Thread(myPriority);
Thread t2 = new Thread(myPriority);
Thread t3 = new Thread(myPriority);
//先设置优先级再启动
t1.setPriority(1);
t1.start();
t2.setPriority(Thread.MAX_PRIORITY);
t2.start();
t3.setPriority(6);
t3.start();
}
static class MyPriority implements Runnable{
public void run(){
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
}
}
}
四、线程同步
1、synchronized
由于一个进程的多个线程共享一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。
- synchronized方法:锁的是类本身
- synchronized块:可以锁任何对象,如synchroniced(account){}锁的是账户这个对象
2、lock
package thread;
import java.util.concurrent.locks.ReentrantLock;
public class test08 {
public static void main(String[] args){
TestLock testLock = new TestLock();
new Thread(testLock).start();
new Thread(testLock).start();
new Thread(testLock).start();
}
}
class TestLock implements Runnable {
int ticketNums = 10;
//定义lock锁
private final ReentrantLock lock = new ReentrantLock();
public void run(){
while(true){
try{
lock.lock();
if(ticketNums>0){
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(ticketNums--);
}else{
break;
}
}finally {
lock.unlock();
}
}
}
}
3、synchronized和lock对比
- Lock是显示锁(手动开启和关闭锁),snchronized是隐式锁(出了作用域自动释放)
- Lock只有代码块锁,synchronized有代码块锁和方法锁
五、线程通信
- wait()方法:表示线程一直等待,直到其他线程通知,与sleep不同,它会释放锁
- wait(long timeout):指定等待的毫秒数
- notify():唤醒一个处于等待状态的线程
- notifyAll():唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度
生产者消费者问题:利用缓冲区解决
package thread;
import com.company.Task4_3;
public class test09 {
public static void main(String[] args){
SynContainer synContainer = new SynContainer();
new Producer(synContainer).start();
new Customer(synContainer).start();
}
}
//生产者
class Producer extends Thread {
SynContainer container;
public Producer(SynContainer container){
this.container = container;
}
//生产
public void run(){
for(int i = 0 ; i < 100 ; i++){
System.out.println("生产了"+i+"只鸡");
container.push(new Chicken(i));
}
}
}
//消费者
class Customer extends Thread {
SynContainer container;
public Customer(SynContainer container){
this.container = container;
}
//消费
public void run(){
for(int i = 0 ; i < 100 ; i++){
System.out.println("消费了-->"+container.pop()+"只鸡");
}
}
}
//产品
class Chicken{
int id;
public Chicken(int id){
this.id = id;
}
}
//缓冲区
class SynContainer {
//容器大小
Chicken[] chickens = new Chicken[10];
//容器计数器
int count = 0 ;
//生产者放入产品
public synchronized void push(Chicken chicken) {
//如果容器满了,就要等待消费者消费
if(count==chickens .length){
try{
this.wait();
}catch(InterruptedException e){
e.printStackTrace();
}
}
chickens[count] = chicken;
count++;
//可以通知消费者消费了
this.notifyAll();
}
//消费者消费产品
public synchronized Chicken pop(){
//判断能否消费
if(count==0){
try{
this.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
count--;
Chicken chicken = chickens[count];
//通知生产者生产
this.notifyAll();
return chicken;
}
}
六、线程池
ExecutorService:线程池接口
- void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
- void shutdown():关闭连接池
package thread;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class test10 {
public static void main(String[] args){
//创建服务,创建线程池
//参数为:线程池大小
ExecutorService service = Executors.newFixedThreadPool(10);
//执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//关闭连接
service.shutdown();
}
}
class MyThread implements Runnable {
public void run(){
System.out.println(Thread.currentThread().getName());
}
}
七、练习
1、熟悉线程生命周期方法。 开启四个线程,两个线程调用锁的 wait 方法,另外两个调用锁的 notify 方法,观察执 行结果并解释原因。
package thread;
import java.util.concurrent.locks.ReentrantLock;
public class task11_01 {
public static void main(String[] args){
TestLock1 testLock1 = new TestLock1();
TestLock2 testLock2 = new TestLock2();
new Thread(testLock1).start();
new Thread(testLock1).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
}
}
class TestLock1 implements Runnable {
public void run(){
System.out.println(Thread.currentThread().getName()+"开始运行");
synchronized ("lock1"){
try{
"lock1".wait();
}catch(InterruptedException e){
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"结束运行");
}
}
class TestLock2 implements Runnable {
public void run(){
System.out.println(Thread.currentThread().getName()+"开始运行");
synchronized ("lock1"){
"lock1".notify();
}
System.out.println(Thread.currentThread().getName()+"结束运行");
}
}
结果:在同一个方法中,连续创建多个线程后,调用线程的start()方法的顺序并不能决定线程的执行顺序。但是利用wait()和notify()方法后,可以保证一定的顺序。只有调用thread3或者thread2中的notify()方法,thread0和thread1才能够结束执行
在 main 函数中开启一个子线程,如果想让 main 主线程在子线程执行完之后才继续执行,代码该怎么写?
package thread;
public class test11_02 {
public static void main(String[] args) throws InterruptedException {
System.out.println("main函数开始执行");
TestThread testThread = new TestThread();
Thread thread = new Thread(testThread);
thread.start();
thread.join();
System.out.println(Thread.currentThread().getName()+"正在执行");
}
}
class TestThread implements Runnable {
public void run(){
System.out.println(Thread.currentThread().getName()+"正在执行");
}
}
2、使用 synchronized 实现抢票程序:某商场做活动,有 100 部 iPhone 可以抽奖兑换。 现在在三个柜台同时兑换,要求所有柜台已兑换的 iPhone 数量加起来刚好是 100,既 不能多换,也不能少换。要求:用 Thread 类实现;用 Runnable 接口实现。
Thread类实现
package thread;
public class test11_03 {
private static int num = 100;
public static void main(String[] args){
new TestThread().start();
new TestThread().start();
new TestThread().start();
}
static class TestThread extends Thread{
public void run(){
while(true){
synchronized ("num"){
if(num>0){
System.out.println(Thread.currentThread().getName()+"在兑换第"+(101-num)+"台手机");
num--;
}
}
}
}
}
}
Runnable接口实现:
package thread;
public class test11_03 {
private static int num = 100;
public static void main(String[] args){
TestThread testThread = new TestThread();
new Thread(testThread).start();
new Thread(testThread).start();
new Thread(testThread).start();
}
static class TestThread implements Runnable{
public void run(){
while(true){
synchronized ("num"){
if(num>0){
System.out.println(Thread.currentThread().getName()+"在兑换第"+(101-num)+"台手机");
num--;
}
}
}
}
}
}
3、使用 newScheduledThreadPool 线程池实现每隔 1 分钟打印一条消息
package thread;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class test11_04 {
public static void main(String[] args) {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
pool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("一分钟过去了");
}
}, 0, 1, TimeUnit.MINUTES);
}
}