java面试之线程相关(不涉及线程池)

一,创建线程的方式及实现
Java中创建线程主要有三种方式:
1、继承Thread类创建线程类
2、通过Runnable接口创建线程类
3、通过Callable和Future创建线程
创建线程的三种方式的对比
采用实现Runnable、Callable接口的方式创见多线程时,优势是:
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
劣势是:
编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
使用继承Thread类的方式创建多线程时优势是:
编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
劣势是:
线程类已经继承了Thread类,所以不能再继承其他父类。
三种方法的具体实现方式

继承Thread类创建线程类

(1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。

(2)创建Thread子类的实例,即创建了线程对象。

(3)调用线程对象的start()方法来启动该线程。

package com.thread;

public class FirstThreadTest extends Thread{
int i = 0;
//重写run方法,run方法的方法体就是现场执行体
public void run()
{
for(;i<100;i++){
System.out.println(getName()+" “+i);
}
}
public static void main(String[] args)
{
for(int i = 0;i< 100;i++)
{
System.out.println(Thread.currentThread().getName()+” : "+i);
if(i==20)
{
new FirstThreadTest().start();
new FirstThreadTest().start();
}
}
}
}
上述代码中Thread.currentThread()方法返回当前正在执行的线程对象。getName()方法返回调用该方法的线程的名字。

二、通过Runnable接口创建线程类

(1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。

(2)创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。

(3)调用线程对象的start()方法来启动该线程。

package com.thread;

public class RunnableThreadTest implements Runnable
{

private int i;
public void run()
{
    for(i = 0;i <100;i++)
    {
        System.out.println(Thread.currentThread().getName()+" "+i);
    }
}
public static void main(String[] args)
{
    for(int i = 0;i < 100;i++)
    {
        System.out.println(Thread.currentThread().getName()+" "+i);
        if(i==20)
        {
            RunnableThreadTest rtt = new RunnableThreadTest();
            new Thread(rtt,"新线程1").start();
            new Thread(rtt,"新线程2").start();
        }
    }
}

}

三、通过Callable和Future创建线程

(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。

(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。

(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。

(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

package com.thread;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallableThreadTest implements Callable
{
public static void main(String[] args)
{
CallableThreadTest ctt = new CallableThreadTest();
FutureTask ft = new FutureTask<>(ctt);
for(int i = 0;i < 100;i++)
{
System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);
if(i==20)
{
new Thread(ft,“有返回值的线程”).start();
}
}
try
{
System.out.println(“子线程的返回值:”+ft.get());
} catch (InterruptedException e)
{
e.printStackTrace();
} catch (ExecutionException e)
{
e.printStackTrace();
}
}
@Override
public Integer call() throws Exception
{
int i = 0;
for(;i<100;i++)
{
System.out.println(Thread.currentThread().getName()+" "+i);
}
return i;
}
}

说说
sleep() 、join()、yield()有什么区别
1、sleep()方法

在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 让其他线程有机会继续执行,但它并不释放对象锁。也就是如果有Synchronized同步块,其他线程仍然不能访问共享数据。注意该方法要捕获异常,持有锁。

比如有两个线程同时执行(没有Synchronized),一个线程优先级为MAX_PRIORITY,另一个为MIN_PRIORITY,如果没有Sleep()方法,只有高优先级的线程执行完成后,低优先级的线程才能执行;但当高优先级的线程sleep(5000)后,低优先级就有机会执行了。
总之,sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会。

2、yield()方法

yield()方法和sleep()方法类似,也不会释放“锁标志”,区别在于,它没有参数,即yield()方法只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行,另外yield()方法只能使同优先级或者高优先级的线程得到执行机会,这也和sleep()方法不同。

3、join()方法

Thread的非静态方法join()让一个线程B“加入”到另外一个线程A的尾部。在A执行完毕之前,B不能工作。

Thread t = new MyThread(); t.start(); t.join();

保证当前线程停止执行,直到该线程所加入的线程完成为止。然而,如果它加入的线程没有存活,则当前线程不需要停止。

作者:Java程序员-张凯
来源:CSDN
原文:https://blog.csdn.net/qq_41701956/article/details/80250226
版权声明:本文为博主原创文章,转载请附上博文链接!

引用参考资料
https://blog.csdn.net/xiangwanpeng/article/details/54972952
sleep(),wait(),yield()和join()方法的区别
2017年02月10日 17:59:37 Alan_Xiang 阅读数:9175
版权声明:本文为博主原创文章,转载请声明原文出处:http://blog.csdn.net/xiangwanpeng https://blog.csdn.net/xiangwanpeng/article/details/54972952
sleep()
  sleep()方法需要指定等待的时间,它可以让当前正在执行的线程在指定的时间内暂停执行,进入阻塞状态,该方法既可以让其他同优先级或者高优先级的线程得到执行的机会,也可以让低优先级的线程得到执行机会。但是sleep()方法不会释放“锁标志”,也就是说如果有synchronized同步块,其他线程仍然不能访问共享数据。
  
wait()
  wait()方法需要和notify()及notifyAll()两个方法一起介绍,这三个方法用于协调多个线程对共享数据的存取,所以必须在synchronized语句块内使用,也就是说,调用wait(),notify()和notifyAll()的任务在调用这些方法前必须拥有对象的锁。注意,它们都是Object类的方法,而不是Thread类的方法。
  wait()方法与sleep()方法的不同之处在于,wait()方法会释放对象的“锁标志”。当调用某一对象的wait()方法后,会使当前线程暂停执行,并将当前线程放入对象等待池中,直到调用了notify()方法后,将从对象等待池中移出任意一个线程并放入锁标志等待池中,只有锁标志等待池中的线程可以获取锁标志,它们随时准备争夺锁的拥有权。当调用了某个对象的notifyAll()方法,会将对象等待池中的所有线程都移动到该对象的锁标志等待池。
  除了使用notify()和notifyAll()方法,还可以使用带毫秒参数的wait(long timeout)方法,效果是在延迟timeout毫秒后,被暂停的线程将被恢复到锁标志等待池。
  此外,wait(),notify()及notifyAll()只能在synchronized语句中使用,但是如果使用的是ReenTrantLock实现同步,该如何达到这三个方法的效果呢?解决方法是使用ReenTrantLock.newCondition()获取一个Condition类对象,然后Condition的await(),signal()以及signalAll()分别对应上面的三个方法。

yield()
  yield()方法和sleep()方法类似,也不会释放“锁标志”,区别在于,它没有参数,即yield()方法只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行,另外yield()方法只能使同优先级或者高优先级的线程得到执行机会,这也和sleep()方法不同。

join()
  join()方法会使当前线程等待调用join()方法的线程结束后才能继续执行,例如:

package concurrent;

public class TestJoin {

public static void main(String[] args) {
    Thread thread = new Thread(new JoinDemo());
    thread.start();

    for (int i = 0; i < 20; i++) {
        System.out.println("主线程第" + i + "次执行!");
        if (i >= 2)
            try {
                // t1线程合并到主线程中,主线程停止执行过程,转而执行t1线程,直到t1执行完毕后继续。
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    }
}

}

class JoinDemo implements Runnable {

@Override
public void run() {
    for (int i = 0; i < 10; i++) {
        System.out.println("线程1第" + i + "次执行!");
    }
}

}

https://www.jianshu.com/p/bc8400654865

补充知识
https://www.imooc.com/article/263221
java 多线程

java 多线程
2018.11.19 23:21 279浏览 举报
java多线程

关于内存
每个线程会有自己的线程栈,即,变量不能共享,只能传值拷贝
每个线程new出的对象全都保存在堆中,全部共享

线程的生命周期
线程具有5种状态,即新建,就绪,运行,阻塞,死亡。
新建,当new出来一个线程以后,jvm为其分配内存空间,并初始化成员变量的值
就绪,当线程调用了strat()方法的时候,线程就绪,会为其创建方法调用栈和程序计数器。

方法调用栈 即,记录方法调用的次数
程序计数器 存放下一条单元指令的地方

运行;就绪状态获得cpu,开始执行run()方法
阻塞:例如进入I/O操作

新建,就绪
使用new关键字创建一个线程以后,该线程处于新建状态,和其他java线程一样,仅仅由java虚拟机为其分配内存,初始化变量成员的值。

运行和阻塞
线程调度
桌面和服务器使用抢占式调度策略,小型设备使用协作式调度策略,

线程阻塞
线程使用sleep()方法主动放弃所占用的处理器资源。
线程调用阻塞式I/O方法,方法被返回前,阻塞
线程等待通知
线程调用suspend()挂起

解除阻塞
依依对应即可

线程优先级
普通5,低1,高10
默认是5

关于start和run
start创建一个线程,由于是时间片运行的,所以需要run方法进行运行。
总结
start创建线程,run方法运行线程。

创建线程
使用Thread继承类实现创建线程
文档 https://docs.oracle.com/javase/8/docs/api/
该类必须重写run方法。为新线程的入口点。
必须调用start()方法才能运行。

本质上是Runnable接口的一种实现

package demo2;

public class test {
public static void main(String[] args) {
// 创建一个线程
ThreadDemo run1 = new ThreadDemo();
run1.start();// 启动线程
// 在运行线程以后,会不定时的jvm调用run方法,进行运行
}
}
package demo2;

public class ThreadDemo extends Thread{
public ThreadDemo() {
System.out.println(“hello world”);
}

public void run() {
	System.out.println("线程进入");
	for(int i = 0; i > 10; i++) {
		System.out.println("输出内容");
	}
	System.out.println("线程执行完毕");
}

}

事实上父类的start方法也可以重写

package demo2;

public class ThreadDemo extends Thread{
public ThreadDemo() {
System.out.println(“hello world”);
}

public void run() {
	System.out.println("线程进入");
	for(int i = 0; i > 10; i++) {
		System.out.println("输出内容");
	}
	System.out.println("线程执行完毕");
}

public void start() {
	System.out.println("启动线程");
	this.run();
}

}
然后测试类

package demo2;

public class test {
public static void main(String[] args) {
// 创建一个线程
ThreadDemo run1 = new ThreadDemo();
run1.start();// 启动线程
}
}
Thread方法
public final void setDaemon(boolean on)
用于标记守护线程和用户线程
用户线程,平常创建的普通线程
守护线程,服务于用户线程,不需要上层调用,例如gc垃圾回收为一个明显的守护线程,mysql中也有执行定时任务的线程。

中断线程
它表示一个线程被中断,会抛出错误。

使用Runnable接口
文档https://docs.oracle.com/javase/8/docs/api/
属于java.lang包内的,为自动默认加载的
该接口具有一个run方法,run方法为程序的入口
必须通过Thread类的构造方法实现启动线程

package demo2;

public class test {
public static void main(String[] args) {
// 创建一个线程
demoRunnable r1 = new demoRunnable();
// 使用Thread类的构造方法传入线程,并起名,然后运行
new Thread(r1, “name”).start();// 创建完成线程以后,调用start启动线程
}
}
package demo2;

public class demoRunnable implements Runnable{
private int i;

// 下方的为运行的线程
@Override
public void run() {
	for(int i = 0; i < 100; i++) {
		System.out.println("运行线程 " + i);
	}
}

}
通过Callable和Future来创建线程
使用Callable创建接口的实现类
接口源码如下

@FunctionalInterface
public interface Callable {
V call() throws Exception;
}
实现了一个泛型,该并且返回该类型,需要实现call方法。使用包装对象

关于包装类型,即,将不是对象的内容包装成为对象,为包装类型,实现了对象的类型,为一个类

先实现Callable接口,其中的call类为程序的子线程的执行体

package demo2;

import java.util.concurrent.Callable;

public class CallableDemo implements Callable{
@Override
public Integer call() throws Exception {
System.out.println(“开始运行一个线程”);
for(int i = 1, i < 10; i++) {
System.out.println(“运行中”);
}
// 阻塞该线程
Thread.sleep(200);
return 1; // 返回线程的值
}
}
接着创建Future对象,将用于启动子线程

package demo2;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class test {
public static void main(String[] args) {
// 先创建实例
CallableDemo ctt = new CallableDemo();
FutureTask ft = new FutureTask<>(ctt); // 该方法为了获取返回值而设定
new Thread(ft, “返回结果的值”).start();
try {
System.out.println(ft.get());
} catch (InterruptedException | ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
package demo2;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class CallableDemo implements Callable{
@Override
public Integer call() throws Exception {
System.out.println(“开始运行一个线程”);
for(int i = 1; i < 10; i++) {
System.out.println(“运行中”);
}
return 1; // 返回线程的值
}

猜你喜欢

转载自blog.csdn.net/qq_35967283/article/details/86499361
今日推荐