Java多线程
一、多线程实现方式
1.继承Thread类,重写run方法
public class ThreadDemo extends Thread{
@Override
public void run(){
//编写自己的线程代码
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args){
ThreadDemo threadDemo = new ThreadDemo();
threadDemo.setName("我是自定义的线程1");
threadDemo.start();
System.out.println(Thread.currentThread().toString());
}
}
优点 :代码简单 。 缺点 :该类无法集成别的类。
2.实现Runnable接口,重写run方法
通过实现Runnable接口,实现run方法,接口的实现类的实例作为Thread的target作为参数传入带参的Thread构造函数,通过调用start()方法启动线程
public class MyThreadRunable implements Runnable{
public static void main(String[] arg){
MyThreadRunable runable = new MyThreadRunable();
Thread myThread = new Thread(runable);
myThread.start();
System.out.println("step2");
}
@Override
public void run() {
System.out.println("step1");
try {
sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
优点 :继承其他类。 同一实现该接口的实例可以共享资源。
缺点 :代码复杂
3.通过Callable和FutureTask创建线程
1)创建Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,且该 call() 方法有返回值 。
2)创建Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象, 该 FutrueTask 对象封装了该 Callable 对象的 call() 方法的返回值。
3)使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
public class ThreadDemo {
public static void main(String[] args) {
Callable<Object> oneCallable = new Tickets<Object>();
FutureTask<Object> oneTask = new FutureTask<Object>(oneCallable);
Thread t = new Thread(oneTask);
System.out.println(Thread.currentThread().getName());
t.start();
}
}
class Tickets<Object> implements Callable<Object>{
//重写call方法
@Override
public Object call() throws Exception {
System.out.println(Thread.currentThread().getName());
return null;
}
}
优点 :可以获得返回值
start() : 作用是启动一个新线程,新线程会执行相应的run()方法。start()不能被重复调用,真正的实现了多线程并发运行。
run() : 只是类的一个普通方法而已,直接调用run方法的话,程序中依然只有主线程这一个线程,其程序执行路径还是要顺序执行,要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到多线程的目的。
二、线程中的状态
java中,每个线程都需经历新生、就绪、运行、阻塞和死亡五种状态,线程从新生到死亡的状态变化称为生命周期。
用new运算符和Thread类或其子类建立一个线程对象后,该线程就处于新生状态。
新生—>就绪:通过调用start()方法。
就绪—>运行:处于就绪状态的线程一旦得到CPU,就进入运行状态并自动调用run()方法。
运行—>阻塞:处于运行状态的线程,执行sleep()方法,或等待I/O设备资源,让出CPU并暂时中止运行,进入阻塞状态。
阻塞—>就绪:睡眠时间已到,或等待的I/O设备空闲下来,线程便进入就绪状态,重新到就绪队列中等待CPU。当再次获得CPU时,便从原来中止位置开始继续运行。
运行—>死亡:(1)线程任务完成;(2)线程被强制性的中止,如通过执行stop()或destroy()方法来终止线程。
三、如何优雅的终止一个线程
线程终止有两种情况:线程的任务执行完成;线程在执行任务过程中发生异常。
1、使用stop()方法,已被弃用。原因是:stop()是立即终止,比较暴力,会带来数据不一致性,所以被废弃。
2、使用退出标志:实现一个Runnable接口,在其中定义volatile标志位,在run()方法中使用标志位控制程序运行,当一个变量被 volatile 修饰时,任何线程对它的写操作都会立即刷新到主内存中,并且会强制让缓存了该变量的线程中的数据清空,必须从主内存重新读取最新数据。
3、使用interrupt()中断的方式,使用interrupt()方法中断正在运行中的线程只会修改中断状态位,可以通过isInterrupted()判断。如果使用interrupt()方法中断阻塞中的线程,那么就会抛出InterruptedException异常,可以通过catch捕获异常,然后进行处理后终止线程。有些情况,我们不能判断线程的状态,所以使用interrupt()方法时一定要慎重考虑。
四、线程中sleep()和wait()有何区别
sleep():使当前线程暂停执行指定的一段时间,但监视状态依然保持,过了指定的时间会自行恢复运行状态。
wait():使当前线程暂停执行,同时释放对象监视器的所有权,直到另一个和它有相同对象监视器的线程调用notify()或者notifyAll()唤醒它,再恢复运行状态。
(1)sleep()不会释放资源,wait()会释放资源;
(2)sleep()是Thread类里的函数,wait()是Object类里的函数;
(3)sleep()可以在任何地方调用,wait()只能在同步方法或者同步代码块中调用(否则会抛IllegalMonitorStateException异常);
五、Runnable接口和Callable接口的区别
Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。
六、多线程同步
(1)synchronized关键字:synchronized有两种用法:synchronized方法和synchronized块
public synchronized void mutithreadAccess();
synchronized(syncObject){代码块}
当一个线程进入一个对象的synchronized()方法后,其他线程是否能够进入此对象的其他方法?
答案:其他线程可进入此对象的非synchronized修饰的方法。如果其他方法有synchronized修饰,都用的是同一对象锁,就不能访问。
如果其他方法是静态方法,且被synchronized修饰,是否可以访问?
答案:可以的,因为static修饰的方法,它用的锁是当前类的字节码,而非静态方法使用的是this,因此可以调用。
(2)Thread类的join()方法
join()方法是Thread中的一个public方法,它有几个重载版本:
1. join()
2. join(long millis) //参数为毫秒
3. join(long millis,int nanoseconds) //第一参数为毫秒,第二个参数为纳秒
(3)wait()方法和notify()方法:当使用synchronized来修饰某个共享资源时,如果线程A1执行synchronized代码,另外一个线程A2也要同时执行同一对象的同一 synchronized代码时,线程A2将要等到线程A1执行完后,才能继续执行。这种情况下可以使用wait()方法和notify()方法。
在synchronized代码被执行期间,线程可以调用对象的wait方法,释放对象锁,进入等待状态,并且可以调用notify()方法或notify()方法通知正在等待的其他线 程。notify()方法仅唤醒一个线程(等待队列中的第一个线程)并允许它去获得锁,notifyAll()方法唤醒所有等待这个对象的线程并允许它们去获得锁。
(4)Lock
ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力
ReenreantLock类的常用方法有:
1. ReentrantLock() : 创建一个ReentrantLock实例
2. lock() : 获得锁
3.unlock() : 释放锁
七、一些编程题
1、编写一个有两个线程的程序,第一个线程用来计算2~100000之间的素数的个数,第二个线程用来计算100000~200000之间的素数的个数,最后输出结果。
package classtest;
public class ThreadSushu extends Thread{
int i,j,x=0;
ThreadSushu (int m,int n){
this.i=m;
this.j=n;
}
public static void main(String[] args) {
ThreadSushu thread1=new ThreadSushu(2,100000);
ThreadSushu thread2=new ThreadSushu(100000,200000);
thread1.start();
thread2.start();
}
public void run(){
int p,q;
p=0;q=0;
for(int m=i;m<=j;m++) {
for(int h=1;h<=m;h++) {
q=m%h;
if(q==0)p=p+1;
}
if(p==2) {
x=x+1;
}
p=0;
}
System.out.println("输出"+i+"到"+j+"之间的质数个数:"+x);
}
}
2、设计2个线程,定义一个变量j,初始值为0。其中第1个线程每个循环对j增加1,循环1000次。另外1个线程对j每次减少1,循环1000次。保证线程安全,最终两个线程执行完j的值仍然是0
package classtest;
public class Thread2 extends Thread{
static int j=0;
static Object lock=new Object();
int flag=0;
Thread2(int flag){
this.flag=flag;
}
public static void main(String[] args) throws InterruptedException {
Thread2 thread1=new Thread2(1);
Thread2 thread2=new Thread2(2);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(j);
}
public void run(){
for(int i=0;i<100000;i++) {
synchronized(lock){
if (flag == 1) {
j=j+2;
} else {
j=j-1;
}
}
}
}
}
3、假如新建T1、T2、T3三个线程,同时启动,如何保证它们按顺序执行?也就是先执行T1,执行完后,再执行T2,执行完后,再执行 T3
package classtest;
public class JoinTest {
public static void main(String[] args) {
Thread t1=new Thread(new Work(null));
Thread t2=new Thread(new Work(t1));
Thread t3=new Thread(new Work(t2));
t1.start();
t2.start();
t3.start();
}
static class Work implements Runnable {
private Thread t;
public Work(Thread t) {
this.t = t;
}
public void run() {
if (t != null) {
try {
t.join();
System.out.println("thread start:"+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
System.out.println("thread start:" + Thread.currentThread().getName());
}
}
}
}
4、写两个线程,一个线程打印1~52,另一个线程打印A~Z,打印顺序是12A34B...5152Z;
package classtest;
public class TwoThread {
public static void main(String args[]){
MyObject1 my = new MyObject1();
new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
for(int i = 0; i < 26; i++){
my.printNum();
}
}
}).start();
new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
for(int i = 0; i < 26; i++){
my.printA();
}
}
}).start();
}
}
class MyObject1{
private static boolean flag = true ;
public int count = 1;
public synchronized void printNum(){
while(flag == false){
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.print((2*count-1));
System.out.print(2*count);
flag = false;
this.notify();
}
public synchronized void printA(){
while(flag == true){
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.print((char)(count+'A'-1));
count++;
flag = true;
this.notify();
}
}
5、编写10个线程,第一个线程从1加到10,第二个线程从11加20…第十个线程从91加到100,最后再把10个线程结果相加。
package classtest;
public class TenThread {
public static class SumThread extends Thread{
int forct = 0; int sum = 0;
SumThread(int forct){
this.forct = forct;
}
@Override
public void run() {
for(int i = 1; i <= 10; i++){
sum += i + forct * 10;
}
System.out.println(getName() + " " + sum);
}
}
public static void main(String[] args) {
int result = 0;
for(int i = 0; i < 10; i++){
SumThread sumThread = new SumThread(i);
sumThread.start();
try {
sumThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
result = result + sumThread.sum;
}
System.out.println("result " + result);
}
}

浙公网安备 33010602011771号