一,问题:
- 什么是线程,为什么要学习它?
- java多线程我们要学习什么?
二,线程(操作系统中的概念):
参考进程:https://blog.youkuaiyun.com/weixin_44841312/article/details/104966035
参考线程:https://blog.youkuaiyun.com/weixin_44841312/article/details/105191973
学习线程之前要知道:
- 并发与并行关系:并发指的是一段时间内多个程勋交替运行,并行指一段时间内多个程勋同时运行。
- 进程:是一个程序执行的过程,是系统运行程序的基本单位。(但你启动QQ程序,本质上内存中就有了QQ这个进程)
- 线程:线程是进程中的小进程,比如QQ程序中的聊天功能就是一个线程,QQ空间又是另一个线程
- 为什么有多线程:为了提高进程效率(程序运行的速度)
- 线程的调度:分时调度(轮流公平调度),抢占式调度(优先级高的可以抢CPU进行调度)
三,java多线程要学习什么?
- 常用创建线程方法
- 线程安全问题
- 线程之间状态转变
- 线程同步的代码实现
- 线程池
四,开始学习:
1,常用创建线程方法:
1,继承Thread类并重写它的run方法。(实现步骤在代码中)
- 下面people继承了Thread类,重写了run方法(该线程要执行的任务),使用people的对象p1和p1调用start方法启动两个线程。start方法会调用run方法
- start方法会在jvm中开辟新的栈空间,对其中的run方法压栈执行
/**
* 实现步骤:
* 1.创建一个Thread类的子类I
* 2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?)
* 3.创建Thread类的子类对象
* 4.调用Thread类中的方法start方法,开启新的线程,执行run方法
* void start() 使该线程开始执行; Java虚拟机调用该线程的run 方法。
* 结果是两个线程并发地运行;当前线程(main线程)和另-一个线程(创建的新线程,执行其run方法)。
* 多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。
* java程序属于抢占式调度,那个线程的优先级高,那个线程优先执行;同-一个优先级,随机选择一个执行
*/
public class RunTest {
public static void main(String[] args) throws InterruptedException {//主线程
People p1=new People("建江");
People p2=new People("小明");
p1.setName("线程1");//设置线程名称
p1.start();//线程1
p2.start();//线程2
for(int i=0;i<10;i++){
System.out.println("main"+i);
Thread.sleep(1000);//线程睡眠1秒
}
}
}
public class People extends Thread {
private String name;
public People(){
}
public People(String name){
this.name=name;
}
@SneakyThrows
public void run(){
for (int i=0;i<10;i++){
System.out.println(name+i);
//String name = getName();//获取线程的名称
String name = Thread.currentThread().getName();
System.out.println("线程名称:"+name);
Thread.sleep(1000);//使得线程睡眠
}
}
}
2,实现实现Runnable接口,实现run方法(实现步骤在代码中)
/**
* 实现步骤:
* 1.创建一个Runnable接口的实现类Dog
* 2.在实现类中重写Runnable接口的run方法,设置线程任务
* 3.创建一个Runnable接口的实现类对象
* 4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
* 5.调用Thread类中的start方法,开启新的线程执行run方法
*/
public class RunTest {
public static void main(String[] args) throws InterruptedException {//主线程
Dog dog1=new Dog("金毛");
Dog dog2=new Dog("泰迪");
Thread thread1=new Thread(dog1);
Thread thread2=new Thread(dog2);
thread1.start();
thread2.start();
}
}
public class Dog implements Runnable {
String name;
public Dog(){}
public Dog(String name){
this.name=name;
}
@Override
public void run() {
for (int i=0;i<10;i++){
System.out.println(name+i);
}
}
}
3,这两种创建线程的方法有什么区别(使用Runable接口实现多线程的好处)
- 避免了单继承的局限性:
- 类继承了Thread类就不能继承其他的类
- 实现了Runnable接口,还可以继承其他的类,实现其他的接口
- 增强了程序的扩展性,降低了程序的耦合性(解耦):
- 实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
- 实现类中,重写了run方法;用来设置线程任务
- 创建Thread类对象,调用start方法:用来开启新线程
4,匿名内部类的方式创建线程
public class RunTest {
public static void main(String[] args){//主线程
new Thread(){
@Override
public void run(){
for (int i=0;i<10;i++){
this.setName("建江");
System.out.println(Thread.currentThread().getName()+i);
}
}
}.start();
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName());
}
}
};
new Thread(runnable).start();
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+i);
}
}
}
2,同步安全问题:
- 什么是同步安全问题:
- 如何解决这个问题:同步代码块,同步方法,锁机制
- 买票的例子(多个线程访问会出现卖重复的票,-1的票)
调用的测试方法:
public class MovieTest {
public static void main(String[] args) {
Movie movie=new Movie();
Thread t1=new Thread(movie);
Thread t2=new Thread(movie);
Thread t3=new Thread(movie);
t1.start();
t2.start();
t3.start();
}
}
使用同步代码块解决:
public class Movie implements Runnable {
private Integer ticket=100;
Object lock=new Object();//创建一个对象锁
@Override
public void run() {
/**
* lock对象只有一个,那个线程得到了lock对象,就可以执行代码块中的内容
* 保证了对临界资源的互斥访问
*/
while (true){
synchronized (lock){//给会出现安全问题的代码加锁
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;
}
}
}
}}
使用同步方法解决:
public class Movie implements Runnable {
private Integer ticket=100;
Object lock=new Object();//创建一个对象锁
@Override
public void run() {
/**
* lock对象只有一个,那个线程得到了lock对象,就可以执行代码块中的内容
* 保证了对临界资源的互斥访问
*/
while (true){
sellTicket();
}
}
public synchronized void sellTicket(){//所对象是this
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
使用Lock机制解决:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Movie implements Runnable {
private Integer ticket=100;
Lock MyLock=new ReentrantLock();//创建一个获取锁的对象
@Override
public void run() {
while (true){
MyLock.lock();//上锁
if (ticket>0){
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
MyLock.unlock();//释放锁
}
}
}
}
}
3,线程间状态的改变:
线程间的状态改变结构如下
消费者,与生产者进程交互调用wait方法和notify方法的案例:(包子铺卖包子)
public class CommuteTest {
private static int i=1;
public static void main(String[] args) {
Object o=new Object();
new Thread(){
@Override
public void run(){
while (true){
synchronized (o){
System.out.println("告诉老板包子的种类和数量,等待5秒");
try {
o.wait();//等待老板做包子
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("顾客"+i+"得到包子,开始吃包子");
System.out.println("顾客"+i+"吃完包子跑了了===========================================");
i++;
}
}
}
}.start();
new Thread(){
@Override
public void run(){
while (true){
try {
Thread.sleep(5000);//花费5秒做包子
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o){
System.out.println("老板做好包子了");
o.notify();
}
}
}
}.start();
}
}
4,线程池?
- 什么是线程池:它是一个存储线程的集合,其中的线程可以借出来使用,使用结束后归还到池子。
- 线程池的优点:线程池中的线程可以被反复使用,减少了资源的消耗,提高了响应速度
- JDK1.5之后有内置线程池。
- 线程池的使用:
线程池:JDK1.5之后提供的
- Executors类 :线程池的工厂类,用来生成线程池
- Executors类中的静态方法:
- newFxedThreadPool(int nThreods) 创建一个可重用固定线程数的线程池,传入参数是线程个数,返回的是值是ExecutorService接口的实现类对象
- ExecutorService:线程池接口:用来从线程池中获取线程,调用start方法,执行线程任务,submit(Runnable task) 提交一个Runnable任务用于执行
线程池的使用步骤:
- 使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
- 创建一个类, 实现Runnable接口,重写run方法,设置线程任务
- 调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法
- 调用ExecutorService中的方法shutdown销毁线程池(不建议执行)
- 具体实现:
public class Dog implements Runnable {
String name;
public Dog(){}
public Dog(String name){
this.name=name;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行了线程");
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class PoolTest {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
Dog dog1=new Dog("金毛");
Dog dog2=new Dog("泰迪");
executorService.submit(dog1);
executorService.submit(dog2);
executorService.submit(new Dog());
executorService.submit(new Dog());
executorService.submit(new Dog());
}
}