创建多线程方式一:集成Thread类
1.创建一个继承自Thread的子类
2.重写Thread中的run方法 线程要做的事情声明在run中
3.创建Thread子类的对象
4.通过此对象调用start方法 start有两个作用 1、启动线程2、调用线程中的run方法
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i%2==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public static class ThreadTest {
public static void main(String[] args) {
MyThread myThread = new MyThread();
//问题一
myThread.start();
//不能直接通过调用线程的run方法 启动线程
// myThread.run();
//问题二:在启动一个线程,继续遍历一百以内都数
// myThread.start(); 会报错 会出现线程状态异常
//要想创建多个线程 就需要new多个对象 一个对象的start方法只能执行一次
MyThread myThread1 =new MyThread();
myThread1.start();
}
}
}
创建多线程方式2
1、创建一个实现Runnable接口的类
2、实现类中实现runnable中的抽象方法
3、创建实现类对象
4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5、通过Thread类的对象,调用start()
比较两种创建方式:
开发过程中优先使用runnable方法创建线程
原因:1、实现的方式没有类中单继承的局限性
2、实现方式更适合来处理多个线程有共享数据的场景情况
联系:
Thead本身也是实现的runnable接口,
相同点:都需要重写run方法,将线程执行的逻辑声明在run中。
class MThread 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 ThreadTest1 {
public static void main(String[] args) {
//创建实现类对象
MThread mThread = new MThread();
//将此对象作为参数传递到Thread类的构造器中,创建Thread对象
Thread thread = new Thread(mThread);
thread.start();//1、启动线程2、调用当前线程的run方法---》d调用的Runnable类型的target的run
//再起一个线程 再次遍历
Thread thread1 = new Thread(mThread);
thread1.start();
}
}
/**
* 创建三个接口卖票
*/
class Window extends Thread{
private static int ticket =100;
@Override
public void run() {
while (true){
if (ticket>0){
System.out.println(getName()+":卖票,票号为:"+ticket);
ticket--;
}else {
break;
}
}
}
}
public class WindosTest {
public static void main(String[] args) {
Window window1 = new Window();
Window window2 = new Window();
Window window3 = new Window();
window1.start();
window2.start();
window3.start();
}
}
此例子有安全的问题
1、买票的过程中出现 重票、错票、---》出现线程安全问题。
2、出现问题的原因是当某个线程操作车票的过程中,尚未操作完成时,其他的线程参与进来,也操作车票
3、当一个线程操作共享数据的时候,其他线程不能参与进来 知道线程a操作完rticket,其他线程才可以参与进来,
即使a线程出现阻塞,也不能被改变。
4、在Java中通过同步机制,解决线程安全问题。
方式一:同步代码块
synchronized(同步监视器){
//需要同步的代码
}
说明:1/操作共享数据的代码 ,即是需要被同步的代码
2/共享数据:多个线程共同操作的变量。比如ticket就是共享数据
3/同步监视器:俗称锁。任何一个类的对象 都可以充当锁
隐藏要求:要求多个线程要共用同一把锁。
补充 在实现Runnable接口创建多线程的方式中,我们可以考虑使用this
方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法同步声明。
5、使用同步的方式,解决了线程安全的问题---好处
操作同步代码时,只能有一个线程参与,其他线程等待。相当于是单线程过程。效率低---缺点
class Window1 implements Runnable{
// private static int ticket= 100;
private int ticket= 100;
Object obj=new Object();
@Override
public void run() {
while (true){
// synchronized(obj){
synchronized (this){//直接调用window1的对象 即可 但是继承类的时候不行
if (ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
ticket--;
}else {
break;
}
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
//关键字不需要用static修饰 因为就创建了一个window1对象
Window1 window1 = new Window1();
Thread thread = new Thread(window1);
Thread thread1 = new Thread(window1);
Thread thread2 = new Thread(window1);
thread.start();
thread1.start();
thread2.start();
}
}
使用同步代码块,解决继承Thread的线程安全问题
在继承Thread类创建多线程的方式中,慎用this充当同步监视器,
class Window2 extends Thread{
private static int ticket =100;
//定义锁 但是在代码中new了三个window2这个类 锁不唯一了
private static Object obj = new Object();
@Override
public void run() {
while (true){
//正确的
// synchronized(obj){
//用类充当监视器 类只会加载一次 意味是唯一的
synchronized(Window2.class){
//错误的 this代表调用时new出来的三个对象
// synchronized(this){
if (ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(getName()+":卖票,票号为:"+ticket);
ticket--;
}else {
break;
}
}
}
}
}
public class WindowTest2 {
public static void main(String[] args) {
Window2 window1 = new Window2();
Window2 window2 = new Window2();
Window2 window3 = new Window2();
window1.start();
window2.start();
window3.start();
}
}
方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法同步声明。
非静态的同步方法 默认的监视器是this
静态方法中 同步监视器是当前类本身
class Window3 implements Runnable{
// private static int ticket= 100;
private int ticket= 100;
Object obj=new Object();
boolean isflag=true;
@Override
public void run() {
while (isflag){
show();
}
}
private synchronized void show (){//此时的同步监视器是this 是唯一的
if (ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
ticket--;
}else {
isflag=false;
}
}
}
public class WindowTest3{
public static void main(String[] args) {
//关键字不需要用static修饰 因为就创建了一个window1对象
Window3 window3 = new Window3();
Thread thread = new Thread(window3);
Thread thread1 = new Thread(window3);
Thread thread2 = new Thread(window3);
thread.start();
thread1.start();
thread2.start();
}
}
class Window4 extends Thread{
private static int ticket =100;
//定义锁 但是在代码中new了三个window2这个类 锁不唯一了
private static Object obj = new Object();
private static boolean isflag= true;
@Override
public void run() {
while (isflag){
show();
}
}
public synchronized void show(){ //此时同步监视器是this 那么和test2中问题一致 this对应的是w1 w2 w3 仍然是线程不安全的
if (ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(getName()+":卖票,票号为:"+ticket);
ticket--;
}else {
isflag=false;
}
}
}
public class WindowTest4 {
public static void main(String[] args) {
Window4 window1 = new Window4();
Window4 window2 = new Window4();
Window4 window3 = new Window4();
window1.start();
window2.start();
window3.start();
}
}
测试Thread常用的方法
1、start()启动当前线程,调用当前线程的run方法
2、run:通常需要重写Thread类中的此方法,将需要执行的具体操作写在run方法中
3、currentThread() 静态方法 返回执行当前代码的线程
4、getName() 获取当前线程名
5、setName() 设置当前线程名
6、yield() :释放当前cpu的执行权,让步 但是不等于让给其他线程执行 还有可能会分配给自己
7、join():在线程a中调用线程b的join()方法 线程a就会进入阻塞状态,在线程b完全执行完之后 线程a才结束阻塞状态。
8、stop():强制结束线程的生命周期,不推荐使用
9、sleep()阻塞当前线程指定毫秒数
10、isAlive()判断当前线程是否存活
线程的优先级
1、MAX_PRIORITY:10
2、MIN_PRIORITY:1
3、NORM_PRIORITY:5 默认是5
如何当前设置线程的优先级
getPriority():获取线程的线程优先级
setPriority(int p):设置线程的优先级
说明:高优先级线程会抢占低优先级线程执行的权限,但是从概率来讲,高优先级线程会高概率被执行,并不意味着只有高优先级线程执行完之后,低优先级的才执行。
public static void main(String[] args) {
HelloThread helloThread = new HelloThread("czcz");
//默认的线程名为:Thread-0
//重新设置线程名 需要在线程执行之前
// helloThread.setName("czThread");
//设置线程的优先级
helloThread.setPriority(Thread.MAX_PRIORITY);
helloThread.start();
/**
* main方法 主线程
*/
//给主线程命名
Thread.currentThread().setName("主线程");
for (int i = 0; i < 100; i++) {
if(i%2==0){
System.out.println(Thread.currentThread().getName()+":"+i+"优先级:"+Thread.currentThread().getPriority());
}
// if(i==20){
// try {
// helloThread.join();
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
// }
}
// boolean alive = helloThread.isAlive();
// System.out.println(alive);
}
class HelloThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if(i%2==0){
System.out.println(Thread.currentThread().getName()+":"+i+"优先级:"+Thread.currentThread().getPriority());
}
// if (i%20==0){
// this.yield();
// }
}
}
//可以通过有参数构造的方法 给线程命名
public HelloThread(String name){
super(name);
}
}
通过实现callable接口实现多线程的操作
与runnable接口的实现方式对比
1、call可以有返回值更加灵活
2、call可以使用throws的方式处理异常,更加灵活
3、callable 使用了泛型参数 可以定义返回值的类型,更加灵活
缺点:
如果在主线程中获取分线程call的返回值,则此时主线程是阻塞状态
class PrintNum implements Callable{
//实现call方法
@Override
public Object call() throws Exception {
int sum=0;
for (int i = 0; i < 100; i++) {
if (i%2==0){
System.out.println(i);
sum+=i;
}
}
return sum;
}
}
public class CallableTest {
public static void main(String[] args) {
//创建callable实现类的对象
PrintNum printNum = new PrintNum();
//将此callable接口实现类的对象作为参数传递到FutureTask的对象构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask<>(printNum);
//将futuretask的对象作为参数传递到Thread类构造器中,创建Thread对象,并调用start方法。
Thread thread = new Thread(futureTask);
thread.start();
try {
//获取callable中call方法的返回值
//get返回值即为futuretask构造器中实现callable中call方法的返回值
Object o = futureTask.get();
System.out.println("相加之和为:"+ o);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
}