1.创建多线程的方法
多线程的创建有四种方法
1.1 继承Thread类
具体有一下四步
1.创建一个继承于Thread类的子类
2.重写Thread类的run()方法
3.创建Thread子类的对象
4.通过此对象调用start()方法
具体的代码如下:
import java.lang.Thread;
class MyThread extends Thread{
@Override
public void run(){
for(int i=0;i<100;i++){
if(i%2==0){
System.out.print(i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args){
MyThread t1 = new MyThread();
t1.start();
for(int i=0;i<10;i++)
System.out.println("hello");
}
}
上述代码中先创建了一个Mythread
对象,这个对象中重写了run方法,是输出0-100之间的偶数,然后在main()中创建了M有thread
的对象,并且调用start方法,start()方法有两个作用,1.启动线程,2.jvm调用当前线程的run方法,然后在main方法中输出十次hello
.
执行结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZUi5f7rL-1602745627805)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20201012172842238.png)]
可以看到run方法和main中hello的输出是交替进行的。
注意:不能通过run方法来启动线程,只能通过start方法,通过run不能够启动线程
1.2 使用匿名子类的方式
如果一个线程,我们只是简单调用一下,可以直接创建一个Thread类的匿名子类
代码如下:
public class ThreadTest {
public static void main(String[] args) {
new Thread() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ':' + i);
}
}
}
}.start();
new Thread() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ':' + i);
}
}
}
}.start();
}
}
在main中创建了两个Thread类的匿名子类,并重写了他们的run方法,然后直接调用,执行结果如下:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kKP5fyjQ-1602745627809)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20201012212052814.png)]
1.3 利用Runnable()接口
具体步骤如下:
1.创建一个实现了Runnable()接口的类
2.重写Thread类的run()
3.创建Thread类的子类对象
4.通过这个对象调用start()
import java.lang.Thread;
class MyThread1 implements Runnable{
@Override
public void run(){
for(int i=0;i<100;i++){
if(i%2==0){
System.out.println(Thread.currentThread().getName()+':'+i);
}
}
}
}
public class ThreadTest {
public ThreadTest() {
super();
}
public static void main(String[] args) {
//创建实现类的对象
MyThread1 t0 = new MyThread1();
//将此对象作为参数传递到Thread类的构造器中
Thread t1 = new Thread(t0);
//通过Thread类的对象调用start()
t1.start();
Thread t2 = new Thread(t0);
t2.start();
}
}
1.4 使用线程池
提前创建好多个线程,放入线程池,使用时直接获取,使用完再放回池中。可以避免频繁的创建和销毁,实现重复利用,能够提高先用速度,降低资源消耗,便于线程管理。
线程池的三个属性:
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务最多保持多久后会终止
2. Thread类中有关的方法
start()
启动当前线程,并且调用当前线程的run()
run()
通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
currentThread()
静态方法,返回执行当前代码的线程
getName()
获取当前线程的名字
setName()
设置当前线程的名字
yield()
释放当前cpu
的执行,让当前的线程让出cpu
join()
在线程A中调用线程B的join()方法,线程A阻塞,直到线程B结束,线程A才结束阻塞
sleep(millis )
调用这个方法的线程阻塞一段时间,时间由参数给定,单位是毫秒
isAlive()
判断这个线程是否存活
getPriority()
获取线程的优先级
setPriority(int p)
设置线程的优先级
3. 线程的优先级
java中已经设置好了三个表示优先级的常量,数越大则说明优先级越高
MAX_PRIORITY:10
MIN_PRIORITY:1
MORM_PRIORITY:5
线程默认的优先级为5,并不是优先级高的线程执行完优先级低的才执行,只是优先级高的线程被执行的概率更高
4.多线程的同步
java中通过同步的机制,来解决线程安全的问题,java实现线程的同步有三种方式
4.1 同步代码块
利用同步监视器,也就是锁,来同步代码块。
任何一个对象都可以都可以来当作锁,多线程必须用同一个所
package com.aiguigu.java;
class Window extends Thread{
private int ticket = 100;
@Override
public void run(){
while(true){
if(ticket>0){
System.out.println(getName()+":卖出的票号为"+ticket);
ticket--;
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window t1 = new Window();
Window t2 = new Window();
Window t3 = new Window();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
在没有加锁的时候,多线程可能会引起各种安全问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f9hD8VSe-1602745627814)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20201013163515408.png)]
通过运行截图可以看到,票号为1的票被窗口3和窗口1卖出去了,相当于一张票卖了两遍,这显然是不行的。通过synchronize()方法来同步代码块就可以解决这个问题
class Window extends Thread{
private int ticket = 100;
Object obj = new Object();
@Override
public void run(){
while(true) {
synchronized (obj) {
if (ticket > 0) {
System.out.println(getName() + ":卖出的票号为" + ticket);
ticket--;
} else break;
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window t1 = new Window();
Window t2 = new Window();
Window t3 = new Window();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
运行结果如下:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SyUX99oT-1602745627818)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20201013163734686.png)]
可以看到,这样就不存在同一张票被卖两次的问题,也就是实现了进程的同步。需要注意的是,synchronized()方法可以用任何的对象来当做锁,但是必须用同一个对象来当做锁。
4.2 同步方法
即把需要同步的代码, 写成一个方法,在方法名前面加上synchronized关键字,将该方法声明为同步的
class Window extends Thread{
private int ticket = 100;
Object obj = new Object();
@Override
public void run(){
while(ticket>0) {
show();
}
}
public synchronized void show(){
if(ticket>0){
System.out.println(getName()+":卖出的票号为"+ticket);
ticket--;
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window t1 = new Window();
Window t2 = new Window();
Window t3 = new Window();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
同步方法中,默认使用的锁为this对象。
5.线程的通信
设计到三个方法:
wait():
一旦执行此方法,当前线程就会进入阻塞状态,并且释放同步监视器
notify()
执行此方法,会唤醒一个被wait阻塞的线程。如果有多个线程被阻塞,就唤醒优先级高的那个
notifyall()
唤醒所有被wait的进程
这三个方法都必须使用在同步方法或者同步代码块中,这三个方法的调用者,必须是同步方法或者同步代码块中的同步监视器,否则会报错,这三个方法是被定义在object()对象中的。
注意:
sleep()和wait()的异同:
相同点:都能使得当前线程进入阻塞
不同点:1.sleep被声明在Thread类中,wait被声明在object对象中
2.调用场景不同,sleep可以在任何需要的场景下调用,wait必须使用在同步方法或者同步代码块中
3.wait释放同步监视器,而sleep不释放