一、方式一:继承Thread类创建线程
步骤:
- 1.继承Thread类
- 2.重写run()方法
- 3.创建线程子类对象
- 4.调用start()方法启动线程
代码示例:
public class Creat_Thread {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
System.out.println("python");
}
}
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"-"+i);
}
}
}
练习:
public class Thread_test01 extends Thread{
public static void main(String[] args) {
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();
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类常用方法:
- start():启动当前线程,并且调用run()
- run():通常需要重写父类中的run(),将自己要执行的业务代码操作写在里面
- currentThread():静态方法,返回执行当前代码的线程
- getName():获取当前线程的名字
- setName():设置当前线程的名字
- yield():释放当前cpu的执行权
- join():在线程a中调用join(),a线程将进入阻塞状态,只有当线程b执行完成后,线程a才继续执行。
- stop():强制结束当前线程
- sleep(long millitime):让当前线程睡眠多少毫秒,进入阻塞转态
- isAlive():判断当前线程是否存活
设置线程的优先级: 1、Java源码中定义的三个默认级别: 也可以自己设置1~10之间的级别,级别越高,cpu的执行权也高,但也不是百分之百优先。只是大概率的会先执行。 2、获取和设置线程的优先级 getPriority():获取线程的优先级 setPriority():设置线程的优先级
二、方式二:实现Runnable接口创建线程
步骤:
- 创建一个实现Runnable接口的类
- 重写run()
- 创建一个Thread类,将实现了Runnable接口的类的实例作为参数
- 调用start()
代码示例:
public class Creat_Runnable {
public static void main(String[] args) {
myRunnable myRunnable = new myRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
}
class myRunnable implements Runnable{
@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种状态:
- 新建:当一个Thread类或其子类的对象被声明并创建时,新的线程对象处于新建状态。
- 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备运行的条件,只是没有分配到CPU资源。
- 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义线程的操作和功能。
- 阻塞:在某种特殊的情况下,被人为挂起或执行输入输出操作时,让出CPU资源并临时终止自己的执行,并进入阻塞状态。
- 死亡:线程完成了它的全部工作或线程被提前强制性地终止或出现异常导致结束。
四、线程安全问题
1.以卖票案例为例,在卖票的时候,多个窗口卖100张票,由于有公用数据会出现线程安全问题。例如,卖出了2张标号为100的票,这是因为:A线程已经开始卖100号票,但还没有彻底执行完成。此时B线程发现100号票还在仓库中没有被卖出去,于是也开始卖100号票。于是两个窗口都在卖100号票。但是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 多线程的创建和使用.买票案例;
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 多线程的创建和使用.买票案例;
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;
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();
@Override
public void run() {
while (true){
try {
lock.lock();
if (ticket > 0){
System.out.println(Thread.currentThread().getName()+":"+ticket);
ticket--;
}else break;
}finally {
lock.unlock();
}
}
}
}
通过lock()和unlock()对需要解决安全问题的代码块进行上锁和解锁,这上锁的时候其他线程不能进入,解锁过后其他线程才能进入。注意:上锁后一定要记得解锁。
面试题: synchronized 与Lock的异同?
- 相同: 二者都可以解决线程安全问题
- 不同: synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器。
Lock需要手动的启动同步(Lock()) ,同时结束同步也需要手动的实现(unLock())
面试题:sleep()和wait()的异同?
- 相同点:执行之后,都可以是当前线程进入阻塞状态。
- 不同点:
- 1)两个方法声明的位置不同:Thread类中声明的sleep(),Object()类中声明wait()
- 2)调用的要求不同:sleep()可以在任何需要的场景下调用。wait()必须使用在同步代码块中。
- 3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步代码方法中,sleep()不会释放锁,wait()会释放锁。
五、方式三:实现Callable接口
- Callable方法比Runable方法更强大
- 相比于run()方法,可以有返回值
- 方法可以抛出异常
- 支持泛型的返回值
- 需要借助FutureTask类,比如获取返回结果
什么是Future接口?
- 可以对你具体Runnable、Callable接口任务的执行结果进行取消、查询是否完成、获取结果等。
- FutureTask是Futrue接口的唯一实现类
- FutureTask同时实现了Runnable、Future接口。它既可以作为Runnable被线程执行,也可以作为Future得到Callable的返回值。
具体步骤:
- 1、实现Callable接口,重写call方法
- 2、在主方法中创建实现Callable接口的类的对象
- 3、创建FutureTask类,传递实现Callable接口的方法的对象
- 4、创建Thread类启动线程
- 5、使用future.get方法获取结果(如果不需要就不写)
案例: 创建线程实现1-100的累加:
package 多线程的创建和使用;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadNew {
public static void main(String[] args) {
NumThread numThread = new NumThread();
FutureTask futureTask = new FutureTask(numThread);
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.*;
public class ThreadPool {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10);
service.submit(new NumberThread1());
NumberThread2 numberThread2 = new NumberThread2();
FutureTask<Integer> futureTask = new FutureTask<Integer>(numberThread2);
service.execute(futureTask);
service.shutdown();
}
}
class NumberThread1 implements Runnable{
@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 {
@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;
}
}
|