步骤:
代码示例:
public class Creat_Thread { public static void main(String[] args) { MyThread myThread = new MyThread();// 3 创建子类 myThread.start();// 4 调用start:①启动线程,②调用run()方法 // 问题一:不能直接调用run()方法启动线程 // 问题二:不能重复启动线程 System.out.println("python"); } } class MyThread extends Thread{// 1 继承Therad类 // 2 重写run()方法 @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+"-"+i); } } }
练习:
/** - @author xhj - 题目:创建两个线程,一个遍历100以内基数、一个遍历100以内偶数 */ public class Thread_test01 extends Thread{ public static void main(String[] args) { // 使用匿名内部类创建线程 new Thread_test01(){ // 重写run()方法 @Override public void run() { for (int i = 0; i < 100; i++) { if (i % 2 == 0){ System.out.println(Thread.currentThread().getName()+"--"+"偶数是:"+i); } } } }.start();// 启动线程,调用run() new Thread_test01(){ @Override public void run() { for (int i = 0; i < 100; i++) { if (i % 2 != 0){ System.out.println(Thread.currentThread().getName()+"--"+"基数是:"+i); } } } }.start(); } }
Thread类常用方法:
设置线程的优先级:
1、Java源码中定义的三个默认级别:
也可以自己设置1~10之间的级别,级别越高,cpu的执行权也高,但也不是百分之百优先。只是大概率的会先执行。
2、获取和设置线程的优先级
getPriority():获取线程的优先级
setPriority():设置线程的优先级
步骤:
代码示例:
/** * @author xhj * 1.创建一个实现Runnable接口的类 * 2.重写run() * 3.创建一个Thread类,将实现了Runnable接口的类的实例作为参数 * 4.调用start() */ public class Creat_Runnable { public static void main(String[] args) { myRunnable myRunnable = new myRunnable(); Thread thread = new Thread(myRunnable); // 3.创建一个Thread类,将实现了Runnable接口的类的实例作为参数 thread.start(); // 4.调用start() } } // 1.创建一个实现Runnable接口的类 class myRunnable implements Runnable{ // 2.重写run() @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+":"+i); } } }
为什么要将Runnable的实例传递给Thread作为参数呢?
通过源码发现:
这里使用了Thread的构造器创建Thread类,Thread.start()调用的是Runnable接口实现方法的run()
两种创建方式的对比:
1、继承Thread方法有单继承的局限性
2、实现Runnable接口的方式更适合多线程共享数据的情况(run()方法都在一个类中)
联系:通过源码发现Thread方法也是实现了Runnable接口,并且重写了run()
相同点:两种方式都需要重写run()方法,并且都将业务代码写在run()中执行。
在实际开发中一般使用实现Runnable的方式。
在JDK中用Thread.State类定义了线程的几种状态,要想实现多线程,必须在主线程中创建新的线程对象。java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常创建要经历如下5种状态:
1.以卖票案例为例,在卖票的时候,多个窗口卖100张票,由于有公用数据会出现线程安全问题。例如,卖出了2张标号为100的票,这是因为:A线程已经开始卖100号票,但还没有彻底执行完成。此时B线程发现100号票还在仓库中没有被卖出去,于是也开始卖100号票。于是两个窗口都在卖100号票。但是100张票总共只有一张,就出现了线程安全问题。
代码:
/** * @author xhj * 创建是三个窗口买票,共100张 */ public class windows extends Thread { private static int ticket = 100; @Override public void run() { while (ticket > 0){ System.out.println(Thread.currentThread().getName()+"买票中...票号:"+ticket); ticket --; } } } class windowsTest{ public static void main(String[] args) { windows windows01 = new windows(); windows windows02 = new windows(); windows windows03 = new windows(); windows01.setPriority(Thread.MAX_PRIORITY); windows01.setName("窗口1"); windows02.setName("窗口2"); windows03.setName("窗口3"); windows01.start(); windows02.start(); windows03.start(); } }
2、解决办法:在Java中采用同步机制来解决线程安全问题
解决方式一:同步代码块
使用synchronized关键字
代码:
package 多线程的创建和使用.买票案例; /** * @author xhj * 创建是三个窗口买票,共100张 */ /* synchronized(同步监视器){ // 需要被同步的代码 } 说明: 1. 操作共享数据的代码,即为需要被同步的代码 2.共享数据:多个线程共同操作的变量。比如: ticket 就是共享数据。 3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。 要求:多个线程必须要共用同一把锁。 */ public class windows extends Thread { private static int ticket = 100; private static Object object = new Object(); //所有线程必须使用同一把锁 @Override public void run() { while (ticket > 0){ synchronized (object){ //加锁 if (ticket > 0) { System.out.println(Thread.currentThread().getName()+"买票中...票号:"+ticket); ticket --; } } } } } class windowsTest{ public static void main(String[] args) { windows windows01 = new windows(); windows windows02 = new windows(); windows windows03 = new windows(); windows01.setPriority(Thread.MAX_PRIORITY); windows01.setName("窗口1"); windows02.setName("窗口2"); windows03.setName("窗口3"); windows01.start(); windows02.start(); windows03.start(); } }
解决方式二:同步方法
在方法上使用synchronized关键字
代码:
package 多线程的创建和使用.买票案例; /** * @author xhj * 创建是三个窗口买票,共100张 */ /* 同步方法:在方法上加synchronized关键字 */ public class windows_ extends Thread { private static int ticket = 100; @Override public void run() { while (true){ show(); } } public static synchronized void show(){ // 同步方法 if (ticket > 0) { System.out.println(Thread.currentThread().getName()+"买票中...票号:"+ticket); ticket --; } } } class windowsTest_{ public static void main(String[] args) { windows_ windows01 = new windows_(); windows_ windows02 = new windows_(); windows_ windows03 = new windows_(); windows01.setPriority(Thread.MAX_PRIORITY); windows01.setName("窗口1"); windows02.setName("窗口2"); windows03.setName("窗口3"); windows01.start(); windows02.start(); windows03.start(); } }
关于同步方法的总结:
1.同步方法仍然涉及到同步监视器,只是不需 要我们显式的声明。
2.非静态的同步方法,同步监视器是: this静态的同步方法,同步监视器是:当前类本身。
线程死锁
1.死锁的理解: 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
2.说明:
1)出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
2)我们使用同步时,要避免出现死锁。
解决线程安全问题方式三:
1.使用lock锁,private ReentrantLock lock = new ReentrantLock();
代码:
package 多线程的创建和使用; import java.util.concurrent.locks.ReentrantLock; /** * 解决线程死锁问题 * @author xhj */ public class Thread_Lock { public static void main(String[] args) { Windows windows = new Windows(); Thread thread1 = new Thread(windows); Thread thread2 = new Thread(windows); Thread thread3 = new Thread(windows); thread1.start(); thread2.start(); thread3.start(); } } class Windows implements Runnable{ private static int ticket = 100; private static ReentrantLock lock = new ReentrantLock();// 创建锁对象实例 //private ReentrantLock lock = new ReentrantLock(true);// 参数默认为false,当为true时,为公平锁。也就是先进先出原则。 @Override public void run() { while (true){ try { // 调用lock()上锁 lock.lock(); if (ticket > 0){ System.out.println(Thread.currentThread().getName()+":"+ticket); ticket--; }else break; }finally { // 解锁 lock.unlock(); } } } }
通过lock()和unlock()对需要解决安全问题的代码块进行上锁和解锁,这上锁的时候其他线程不能进入,解锁过后其他线程才能进入。注意:上锁后一定要记得解锁。
面试题: synchronized 与Lock的异同?
面试题:sleep()和wait()的异同?
什么是Future接口?
具体步骤:
案例:
创建线程实现1-100的累加:
package 多线程的创建和使用; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /** * @author xhj * 创建线程的方式三:实现Callable接口 * 1、实现Callable接口,重写call方法 * 2、在主方法中创建实现Callable接口的类的对象 * 3、创建FutureTask类,传递实现Callable接口的方法的对象为参数 * 4、创建Thread类启动线程 * 5、使用future.get方法获取结果(如果不需要就不写) */ public class ThreadNew { public static void main(String[] args) { NumThread numThread = new NumThread();//创建实现了Callable接口的类 FutureTask futureTask = new FutureTask(numThread);//创建FutureTask类,借助这个类对返回的结果进行获取 new Thread(futureTask).start();//启动线程 try { Object sum = futureTask.get(); //获取结果 System.out.println(sum); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } class NumThread implements Callable { @Override public Object call() throws Exception { int sum = 0; for (int i = 1; i <= 100; i++) { sum = i + sum; System.out.println(i); } return sum;//返回结果 } }
案例:
package 多线程的创建和使用; import java.util.concurrent.*; /** * @author xhj * 创建线程的方式四:使用线程池 * * */ public class ThreadPool { public static void main(String[] args) { //1、指定线程池的线程数量为10 ExecutorService service = Executors.newFixedThreadPool(10); //2、将线程作为参数传递 service.submit(new NumberThread1()); //submit方法适用执行的是Callable创建的线程 NumberThread2 numberThread2 = new NumberThread2(); FutureTask<Integer> futureTask = new FutureTask<Integer>(numberThread2); service.execute(futureTask); //execute方法适用执行的是Runnable和Thread创建的线程 service.shutdown(); //3、关闭线程池,释放资源 } } class NumberThread1 implements Runnable{ //遍历100以内的偶数 @Override public void run() { int sum = 0; for (int i = 1; i <= 100; i++) { if (i % 2 == 0){ System.out.println(Thread.currentThread().getName() + "==" + i); sum = i + sum; } } } } class NumberThread2 implements Callable { //遍历100以内的奇数 @Override public Object call() throws Exception { int sum = 0; for (int i = 1; i <= 100; i++) { if (i % 2 != 0){ System.out.println(Thread.currentThread().getName() + "==" + i); sum = i + sum; } } return sum; } }