线程的三种启动方式
第一种线程启动方式
继承Tread类,重写run方法
public class MyThread extends Thread{
@Override
public void run() {
//书写线程执行的代码
for(int i=0; i<100; i++){
System.out.println(getName() + "HelloWorld");
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
// 多线程的第一种启动方式:
//1 自己定义一个类基础Thread
//2 重写run方法
//3 创建子类的对象,并启动线程
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
//设置线程的名字
t1.setName("线程一");
t2.setName("线程二");
//通过start启动线程
t1.start();
t2.start();
}
}
第二种实现Runnable接口
public class MyThread implements Runnable{
@Override
public void run() {
//书写线程执行的代码
for(int i=0; i<100; i++){
//getName()是Thread的方法,所以先把当前Tread找出
System.out.println(Thread.currentThread().getName() + "HelloWorld");
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
// 多线程的第二种启动方式:
//1 自己定义一个类实现Runnable接口
//2 重写里面的run方法
//3 创建自己类的对象
//4 创建一个Thread类的对象,并开启线程
//创建MyThread对象
MyThread mt = new MyThread();
//创建线程对象
Thread t1 = new Thread(mt);
Thread t2 = new Thread(mt);
//设置线程的名字
t1.setName("线程一");
t2.setName("线程二");
//通过start启动线程
t1.start();
t2.start();
}
}
第三种实现Callable接口(实现该接口的线程,可返回线程结果)
public class MyThread implements Runnable{
@Override
public void run() {
//书写线程执行的代码
for(int i=0; i<100; i++){
//getName()是Thread的方法,所以先把当前Tread找出
System.out.println(Thread.currentThread().getName() + "HelloWorld");
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
// 多线程的第二种启动方式:
//1 自己定义一个类实现Runnable接口
//2 重写里面的run方法
//3 创建自己类的对象
//4 创建一个Thread类的对象,并开启线程
//创建MyThread对象
MyThread mt = new MyThread();
//创建线程对象
Thread t1 = new Thread(mt);
Thread t2 = new Thread(mt);
//设置线程的名字
t1.setName("线程一");
t2.setName("线程二");
//通过start启动线程
t1.start();
t2.start();
}
}
常见的成员方法
String getName() 返回此线程的名称
void setName() 设置线程的名字(构造方法也可以设置名字)
构造方法指的是 Thread的构造方法
继承类需要写自己的构造类
public MyThread(String name){
super(name);
}
这样设置即可构造方法设置名字
static Thread currentThread( ) 获取当前线程的对象
细节:当JVM虚拟机启动之后,会自动的启动多条线程,其中一条线程就叫做main线程,她的作用就是去调用main方法,并执行里面的代码
static void sleep(long time) 休眠线程,单位为毫秒
线程的优先级 0-10 默认5
sePriority()
Thread t1 = new Thread()
t1.sePriority();
虽然优先级越高,执行的顺序越前,但也不是绝对,只是概率越高
线程的调度
1、抢占式调度(自由度高)随机 java的选择
2、非抢占式调度
守护线程
final void setDaemon(boolean on)
当其他的非守护线程执行完毕之后,守护线程会陆续结束
MyThread1 t1 = new MyThread1();
MyThread2 t2 = new MyThread2();
t2.setDaemon(true);
t1.start();
t2.start();
t1为非守护线程,则当t1结束时,t2也会跟着结束
礼让线程和插入线程
礼让线程(public static void yield)写在重写线程类中的run方法
public class MyThread extends Thread{
@Override
public void run(){
for(int i=1;i<=100;i++){
System.out.println(getName() + i);
//表示出让当前CPU的执行权
Thread.yield();
}
}
}
插入线程public final void join( )
MyThread t= new MyThread();
t.join();
线程的生命周期
就是计算机系统线程的五大周期
创建 就绪 执行 结束
阻塞
线程的安全问题
同步代码块
//obj一定要唯一,则一般传入的是当前类的字节码对象 例如该类为MyThread,则obj = MyThread.class
synchronized( obj ){
}
则其中的代码边上锁了,同一时间只有一个用户运行执行,synchronized是自动上锁、自动解锁
细节:1、一定要写在循环里面synchronized( obj );
2、obj一定要是唯一的,不唯一就相当于有多把锁,则无法对代码块进行限制
public class MyThread extends Thread{
//加上static后,表示这个类所有的对象,都共享ticket数据(静态对象)
static int ticket = 0;//0~99
//锁对象,一定要是唯一的(则加上static修饰符)
static Object obj = new Object();
@Override
public void run(){
while(true){
//同步代码块
synchronized(obj){
....
}
}
}
}
同步方法(StringBuilder就是线程不安全,StringBuffer线程安全)
修饰符 synchronized 返回值类型 方法名(方法参数){…}
特点1:同步方法是锁住方法里面的所有代码
特点2:锁对象不能自己指定,而是由java指定 非静态方法时: this
静态方法时: 当前类的字节码文件对象
书写方法:1.循环 2.写同步代码块 3.把同步代码块提取为方法
lock锁,lock锁可以自行决定何时上锁、何时解锁
//Lock是一个接口,所有创建对象时,要用new它的实现类ReentrantLock
//使用第一种方式创建线程时,一定要给这样 修饰 static
static Lock lock = new ReentrantLock();
@Override
public void run(){
while(){
lock.lock();
try{
...
}catch(){
...
}finally{
lock.unlock();
}
}
}
生产者消费者(常见方法)
pv方式
void wait() 当前线程等待,直到被其他线程唤醒
void notify() 随机唤醒单个线程
void notifyAll() 唤醒所有线程
在资源类中定义锁对象,然后synchronized(锁对象),然后由锁对象来使用wait()或notify()
就是计算机操作系统的pv操作,找一个类定义好资源,定义好锁对象,然后生产者消费者之间相互PV
等待队列 (不用我们去控制pv操作了)
阻塞队列的继承结构
Iterable
↓
Collection 接口继承接口 4个
↓
Queue
↓
BlockingQueue
ArrayBlockingQueue LinkedBlockingQueue 两个实现类
底层数组,有界 底层链表,无界
Cook
public class Cook extends Thread(){
ArrayBlockingQueue<String> queue;
//透过构造方法才能使生产者和消费者用到一个队列,在main方法中定义时传入
public Cook(ArrayBlockingQueue<String> queue){
this.queue = queue;
}
@Override
public void run(){
while(true){
try{
//不断放面条,只要队列未满则可一直放,满了则阻塞
queue.put("面条");
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
Foodie
public class Foodie extends Thread(){
ArrayBlockingQueue<String> queue;
//透过构造方法才能使生产者和消费者用到一个队列,在main方法中定义时传入
public Cook(ArrayBlockingQueue<String> queue){
this.queue = queue;
}
@Override
public void run(){
while(true){
try{
//不断吃面条,只要队列为空则阻塞
queue.take("面条");
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
Main
public class ThreadDemo{
public static void main(String[] args){
//生产者消费者一定要在同一阻塞队列中
//底层为数组的阻塞队列,要自己设置大小
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
//创建线程并传递队列
Cook c = new Cook(queue);
Foodie f = new Foodie(queue);
c.start();
f.start();
}
}
多线程的6种状态
新建状态(NEW) --> 创建线程对象
就绪状态(RUNNABLE) --> start方法
阻塞状态(BLOCKED) --> 无法获得锁对象
等待状态(WAITING) --> wait方法
计时等待(TIMED_WATING) --> SLEEP方法
结束状态(TERMINATED) --> 全部代码运行完毕
线程池
线程池主要就是为了能够重复利用线程,一般创建个线程用完后,不销毁,放入线程池中即可。
Executors: 线程池的工具类通过调用方法返回不同类型的线程池对象。
方法名称
public static ExecutorService newCachedThreadPool() 创建一个没有上限的线程池(假没有上限,上限为int)
public static ExecutorService newFixedThreadPool(int nThreads) 创建有上限的线程池
public class MyThreadPoolDemo {
public static void main(String[] args){
//1.获取线程池对象(无上限版)
ExecutorService pool1 = Executors.newCachedThreadPool();
//2.提交任务
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
//3.销毁线程池 但是我们一般是不销毁线程池的
//pool1.shutdown();
}
}
public class MyRunnable implements Runnable{
@Override
public void run() {
for(int i=1;i<10;i++){
System.out.println(Thread.currentThread().getName()+i);
}
}
}
自定义线程池
只有核心线程占满后,且队列也排队排满了,则才创建临时线程
public class MyThreadPoolDemo1{

}

任务拒绝策略(到核心线程、队列、临时线程全满后,开启该策略)

文章介绍了Java中实现多线程的三种方式:继承Thread类、实现Runnable接口以及实现Callable接口。详细讲解了线程的启动、成员方法,包括线程的生命周期、优先级、守护线程、线程安全问题,如同步代码块、同步方法和Lock锁。此外,还提到了生产者消费者模式下的阻塞队列以及线程池的概念,如ExecutorService和线程池的创建及自定义。
4492

被折叠的 条评论
为什么被折叠?



