创建线程的三种方式
方式一:继承Thread类
实现步骤:
-
继承Thread类并重写run()方法;
-
创建线程并启动。
代码实现:
public class MyThread extends Thread {
@Override
public void run() {
for(int i=0; i<100; i++) {
System.out.println(i);
}
}
}
创建两个线程测试一下:
public class MyThreadDemo {
public static void main(String[] args) {
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
my1.start();
my2.start();
}
}
输出结果如下:
-
为什么要重写run()方法?
因为run()中封装被线程执行的代码。
方式二:实现Runnable接口
Thread构造方法
方法名 | 说明 |
---|---|
Thread(Runnable target) | 创建一个线程 |
Thread(Runnable target, String name) | 创建一个线程,线程名字为name |
实现步骤:
-
实现Runnable接口并重写run()方法,然后创建实现类的对象,表示线程要执行的任务;
-
把Runnable接口实现类的对象作为构造方法的参数创建一个线程并启动;
代码实现:
public class RunDemo1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread() + "" + i);
}
}
}
创建线程对象:
public class TestDemo2 {
public static void main(String[] args) {
RunDemo1 r = new RunDemo1();
// 其中r表示线程要执行的任务
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
}
}
方式三:实现Callable接口
由于前两种方式的run方法都是没有返回值的,第三种可以获取返回值。
方法介绍:
方法名 | 说明 |
---|---|
V call() | 计算结果,如果无法计算结果,则抛出一个异常 |
FutureTask(Callable<V> callable) | 创建一个 FutureTask,一旦运行就执行给定的 Callable |
V get() | 用来获取执行结果 |
实现步骤: 实现 Callable 接口,重写 call 方法,这种方式可以通过 FutureTask 获取任务执行的返回值。
代码实现:
public class MyCallable implements Callable {
@Override
public Object call() throws Exception {
return "nihao";
}
}
测试一下:
public class TestDemo3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable cl = new MyCallable();
FutureTask<String> ft = new FutureTask<String>(cl);
Thread t = new Thread(ft);
t.start();
System.out.println(ft.get());
}
}
输出结果:
线程中常用的方法
设置和获取线程名称的方法
方法名 | 说明 |
---|---|
void setName(String name) | 将线程的名字设置为参数name |
String getName() | 返回此线程的名字 |
static Thread currentThread() | 返回执行到这行代码的线程 |
休眠线程方法
方法名 | 说明 |
---|---|
static void sleep(long millis) | 使当前正在执行的线程暂停指定的毫秒数 |
需要注意的是,sleep 的时候要对异常进行处理。
try {//sleep会发生异常要显示处理
Thread.sleep(20);//暂停20毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
线程优先级相关方法
方法名 | 说明 |
---|---|
final int getPriority() | 返回此线程的优先级 |
final void setPriority(int newPriority) | 更改线程的优先级,线程默认优先级是5 |
如果获取main线程的优先级呢?
首先使用Thread.currentThread()获取到main线程对象;
然后再使用getPriority()获取优先级。
Thread类中与优先级有关的代码,可以看到线程优先级的范围是1-10。
// The minimum priority that a thread can have.
public static final int MIN_PRIORITY = 1;
// The default priority that is assigned to a thread.
public static final int NORM_PRIORITY = 5;
// The maximum priority that a thread can have.
public static final int MAX_PRIORITY = 10;
数字越大表示优先级越高,cpu执行这个线程的概率越高,反之越低。
优先级只是概率问题,并不是绝对的。
下面举一个例子:
public class TestDemo1 {
public static void main(String[] args) {
ThreadDemo1 t1 = new ThreadDemo1("线程1:");
ThreadDemo1 t2 = new ThreadDemo1("线程2:");
t1.setPriority(1);
t2.setPriority(10);
t1.start();
t2.start();
}
}
程序的执行结果中是有可能优先级低的线程1先结束执行的。这里就不提供图片了。
守护线程方法
方法名 | 说明 |
---|---|
void setDaemon(boolean on) | 将此线程标记为守护线程,当非守护线程结束后守护线程也慢慢结束 |
ThreadDemo1中的run方法:
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(this.getName() + ":" + i);
}
}
ThreadDemo2中的run方法:
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(this.getName() + ":" + i);
}
}
创建两个线程,然后将线程t2设置为守护线程,代码如下:
public class TestDemo4 {
public static void main(String[] args) {
ThreadDemo1 t1 = new ThreadDemo1();
ThreadDemo2 t2 = new ThreadDemo2();
// 将线程t2设置为守护线程
t2.setDaemon(true);
t1.start();
t2.start();
}
}
可以看到在非守护线程t1结束之后,守护线程t2也慢慢结束了。
礼让线程方法
Java 中的优先级不是特别的可靠,Java 程序中对线程所设置的优先级只是给操作系统一个建议,操作系统不一定会采纳。而真正的调用顺序,是由操作系统的线程调度算法来决定的。
插入线程方法
比如将线程t1插入到当前正在运行的main线程之前:
public class TestDemo5 {
public static void main(String[] args) throws InterruptedException {
ThreadDemo1 t1 = new ThreadDemo1();
t1.start();
t1.join();
for (int i = 0; i < 40; i++) {
System.out.println("main线程:" + i);
}
}
}
运行结果:
Java 线程的 6 个状态:
// Thread.State 源码
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
线程的3个状态:就绪,运行,阻塞。图示如下:
cpu执行哪个线程是由操作系统进行调度的,有一些常见的线程调度算法,比如先来先服务,最短作业优先,最短剩余时间优先,轮转法等等。
synchronized
同步代码块
多线程并发执行可能会出现数据安全问题,比如下面的三个窗口卖100张票的例子。
刚开始代码是这样写的:
public class MyThread1 extends Thread{
static int ticket = 0;
@Override
public void run() {
while (true) {
if (ticket > 99) {
break;
} else {
ticket++;
System.out.println(getName() + "卖了第" + ticket + "张票");
}
}
}
}
有两个问题:①相同的票;②超过范围的票。
下面是正确的代码,加锁的目的是为了使操作具有原子性,当锁没有释放时,别的线程是不能执行这段代码的,一般锁对象用static修饰保证唯一性。
public class MyThread1 extends Thread {
static int ticket = 0;
final static Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj) {
if (ticket > 99) {
break;
} else {
ticket++;
System.out.println(getName() + "卖了第" + ticket + "张票");
}
}
}
}
}
如果输出结果的顺序是混乱的,比如下面这种情况,是为什么呢?
因为锁没有起作用,比如定义创建一个锁的代码,因为在创建每一个新的线程的时候都会创建一个obj,锁不唯一。
final Object obj = new Object();
同步方法
用synchronized关键字修饰的方法。synchronized关键字写到访问修饰符的后面。
特点:
synchronized方法提供了一个锁,非静态方法的锁是调用此方法的对象,静态方法的锁是字节码文件。锁默认是打开的,当有一个线程进去之后,锁关闭,当代码执行完毕之后锁打开。
线程在哪里?
执行这行代码的线程。
尝试将上面例子中同步代码块的代码抽取为一个同步方法,代码如下,但是不对,因为同步方法中的锁是调用此方法的对象,此时有三个锁,分别是3个线程,锁不唯一。
public class MyThread1 extends Thread {
static int ticket = 0;
final static Object obj = new Object();
@Override
public void run() {
while (true) {
if (saleTicket()) break;
}
}
private synchronized boolean saleTicket() {
if (ticket > 99) {
return true;
} else {
ticket++;
System.out.println(getName() + "卖了第" + ticket + "张票");
}
return false;
}
}
所以同步方法一般使用Runnable接口,因为这种情况下锁唯一。
public class MyRunnable implements Runnable{
static int ticket = 0;
final static Object obj = new Object();
@Override
public void run() {
while (true) {
if (saleTicket()) break;
}
}
private synchronized boolean saleTicket() {
if (ticket > 99) {
return true;
} else {
ticket++;
System.out.println(Thread.currentThread().getName() + "卖了第" + ticket + "张票");
}
return false;
}
}
Lock锁
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock。Lock是接口,不能直接实例化,这里采用它的实现类ReentrantLock来实例化。
-
ReentrantLock构造方法
方法名 说明 ReentrantLock() 创建一个ReentrantLock的实例 -
加锁解锁方法
方法名 说明 void lock() 获得锁 void unlock() 释放锁
代码如下,有两个细节:
①锁用static修饰,唯一性。
②当某个线程进入锁时,由于ticket < 100为false,执行else里的break结束while循环,但是并没有释放锁,所以程序无法结束,可以将lock.unlock();放到finally子句中确保锁一定会释放。
有一个疑问:用synchronized的锁是什么释放的?
在大括号结束时释放的。
public class MyThread1 extends Thread {
static int ticket = 0;
final static Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();
try {
if (ticket > 99) {
break;
} else {
ticket++;
System.out.println(getName() + "卖了第" + ticket + "张票");
}
} finally {
lock.unlock();
}
}
}
}
子父类有关异常的处理?
生产者消费者模式(等待唤醒机制)
由于线程之间是抢占式执⾏的,因此线程之间执⾏的先后顺序难以预知。但是实际开发中希望协调多个线程的执行顺序,实现线程间的协作。
在 Java 中可以用 wait、notify 和 notifyAll 来实现线程间的通信。
wait和notify两个方法被提取到顶级父类Object中。Object类的等待和唤醒方法:
方法名 | 说明 |
---|---|
void wait() | 让当前线程进⼊等待状态,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法 |
void notify() | 随机唤醒等待队列中的一个线程 |
void notifyAll() | 唤醒等待队列中的所有线程 |
线程在运行的时候,如果发现某些条件没有被满足,可以调用wait方法放弃已经获得的锁,并且暂停自己的执行,然后进入等待状态。
当该线程被其他线程唤醒并获得锁后,可以沿着之前暂停的地方继续向后执行,而不是再次从同步代码块开始的地方开始执行。
但是需要注意的一点是,在线程通信中涉及到条件判断时要用while而不是if。因为while语句来说,这样在线程被唤醒后,会再次判断条件是否正真满足。
信号量实现
public class Food {
static boolean flag;
public synchronized static void eat() {
while (!flag){
try {
Food.class.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("小张正在吃汉堡");
flag = !flag;
Food.class.notifyAll();
}
public synchronized static void cook() {
while (flag){
try {
Food.class.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("小刘正在做法式大餐");
flag = !flag;
Food.class.notifyAll();
}
}
public class Consumer extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
Food.eat();
}
}
}
public class Producer extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
Food.cook();
}
}
}
阻塞队列实现
即生产者和消费者中间有一个缓冲区或者叫容器用来存放产品,可以存放多个。
(1)当CPU执行到生产者这个线程时:
①首先判断容器里产品是否已满;
②已满就调用wait方法使自己进入等待状态;
③没有则生产,使产品数量+1,并唤醒等待的消费者进行消费。
关于其中的notifyAll:由于没有产品可消费了,消费者处于等待状态,当生产者生产了产品后,唤醒消费者。
(2)当CPU执行到消费者这个线程时:
①首先判断容器里是否有产品;
②没有就调用wait方法使自己进入等待状态;
③有则消费,使产品数量-1,并唤醒等待的生产者进行生产。
实现如下:
产品描述Product类代码如下:
public class Product {
int id;
public Product(int id) {
this.id = id;
}
}
用来装产品的容器类SynContainer代码如下:
public class SynContainer {
Product[] products = new Product[10];
// 既表示产品的个数,也表示生产的产品存入数组中的下标
int count = 0;
public synchronized void put(Product p) {
// 判断容器是否满了
// 1. 满了
while (count == products.length) {
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
products[count] = p;
count++;
this.notifyAll();
}
public synchronized Product take() {
// 首先判断容器里是否有产品
// 1. 没有
if (count == 0) {
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
count--;
this.notifyAll();
return products[count];
}
}
生产者Producer的代码如下:
public class Producer extends Thread{
SynContainer synContainer;
public Producer(SynContainer synContainer) {
this.synContainer = synContainer;
}
@Override
public void run() {
for (int i = 1; i < 100; i++) {
synContainer.put(new Product(i));
System.out.println("生产者生产了第" + i + "个产品");
}
}
}
消费者Consumer代码如下:
public class Consumer extends Thread{
SynContainer synContainer;
public Consumer(SynContainer synContainer) {
this.synContainer = synContainer;
}
@Override
public void run() {
for (int i = 1; i < 100; i++) {
System.out.println("消费者消费了第" + synContainer.take().id + "个产品");
}
}
}
测试类代码如下:
public class Test {
public static void main(String[] args) {
SynContainer synContainer = new SynContainer();
Producer producer = new Producer(synContainer);
Consumer consumer = new Consumer(synContainer);
producer.start();
consumer.start();
}
}
由于线程中的run方法并没有使用锁,因此输出的时候顺序会乱,如下:
还有一个细节,如果如何确保生产者和消费者使用的是同一个容器,即在Producer和Consumer类中只声明一个SynContainer变量,由各自的构造方法传递实际的容器类对象。
标签:Java,Thread,void,static,线程,new,public From: https://blog.csdn.net/m0_46977298/article/details/140572571