为什么要使用多线程?
- 利用多线程提高程序的扩展能力,以达到业务对吞吐量的要求
- 协调线程间交互、调度,以完成业务逻辑
多线程付出的代价:
1)设计更复杂
虽然有一些多线程应用程序比单线程的应用程序要简单,但其他的一般都更复杂。在多线程访问共享数据的时候,这部分代码需要特别的注意。线程之间的交互往往非常复杂。不正确的线程同步产生的错误非常难以被发现,并且重现以修复。
2)上下文切换的开销
当CPU从执行一个线程切换到执行另外一个线程的时候,它需要先存储当前线程的本地的数据,程序指针等,然后载入另一个线程的本地数据,程序指针等,最后才开始执行。这种切换称为“上下文切换”(“context switch”)。CPU会在一个上下文中执行一个线程,然后切换到另外一个上下文中执行另外一个线程。上下文切换并不廉价。如果没有必要,应该减少上下文切换的发生。
多线程的生命周期:
新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态
描述:
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态。尤其是当线程启动以后,它不可能一直"霸占"着CPU独自运行,所以CPU需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换
新建状态,当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时仅由JVM为其分配内存,并初始化其成员变量的值
线程对象 别名=new 线程对象();
就绪状态,当线程对象调用了start()方法之后,该线程处于就绪状态。Java虚拟机会为其创建方法调用栈和程序计数器,等待调度运行
运行状态,如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态
线程别名.start(); 启动线程运行状态
阻塞状态,当处于运行状态的线程失去所占用资源之后,便进入阻塞状态
死亡状态,线程在run()方法执行结束后进入死亡状态。此外,如果线程执行了interrupt()或stop()方法,那么它也会以异常退出的方式进入死亡状态。
上代码:
创建线程:
第一种方法:
继承Thread类, Thread实现了runnable接口
public class NewThread extends Thread {
@Override
public void run() {
System.out.println("运行线程~~");
}
public static void main(String[] args) {
NewThread newThread=new NewThread();
newThread.start();
}
}
第二种方法:实现Runnable接口
实现了 runnable接口
public class NewThreadImpl implements Runnable {
@Override
public void run() {
System.out.println("必须要重写run方法");
}
public static void main(String[] args) {
Thread thread=new Thread(new NewThreadImpl());
thread.start();
}
}
Synchronized:
Synchronized主要是通过对象锁的方式来实现线程安全(这边的锁是指虚拟机实现的锁,和Java Lock类库不是一个概念),修饰静态方法获取的是Class对象的锁,修饰方法是获取当前对象this的锁,修饰括号内容是获取括号内对象的锁。
Concurrent
Java类库中有专门的concurrent包实现线程安全,核心类就是AQS(AbstractQueuedSynchronizer),一个抽象队列同步器,相当于一个模板,类库中ReentantLock等都是通过它来实现的。
并发容器:
这些容器的关键方法大部分都实现了线程安全的功能,却不使用同步关键字 (synchronized)。值得注意的是 Queue 接口本身定义的几个常用方法的区别,
add 方法和 offer 方法的区别在于超出容量限制时前者抛出异常,后者返回 false;
remove 方法和 poll 方法都从队列中拿掉元素并返回,但是他们的区别在于空队列下操作前者抛出异常,而后者返回 null;
element 方法和 peek 方法都返回队列顶端的元素,但是不把元素从队列中删掉,区别在于前者在空队列的时候抛出异常,后者返回 null。
阻塞队列:
BlockingQueue.class,阻塞队列接口
BlockingDeque.class,双端阻塞队列接口
ArrayBlockingQueue.class,阻塞队列,数组实现
LinkedBlockingDeque.class,阻塞双端队列,链表实现
LinkedBlockingQueue.class,阻塞队列,链表实现
DelayQueue.class,阻塞队列,并且元素是 Delay 的子类,保证元素在达到一定时间后才可以取得到
PriorityBlockingQueue.class,优先级阻塞队列
SynchronousQueue.class,同步队列,但是队列长度为 0,生产者放入队列的操作会被阻塞,直到消费者过来取,所以这个队列根本不需要空间存放元素;有点像一个独木桥,一次只能一人通过,还不能在桥上停留
非阻塞队列:
ConcurrentLinkedDeque.class,非阻塞双端队列,链表实现
ConcurrentLinkedQueue.class,非阻塞队列,链表实现
转移队列:
TransferQueue.class,转移队列接口,生产者要等消费者消费的队列,生产者尝试把元素直接转移给消费者
LinkedTransferQueue.class,转移队列的链表实现,它比 SynchronousQueue 更快
其它容器:
ConcurrentMap.class,并发 Map 的接口,定义了 putIfAbsent(k,v)、remove(k,v)、replace(k,oldV,newV)、replace(k,v) 这四个并发场景下特定的方法
ConcurrentHashMap.class,并发 HashMap
ConcurrentNavigableMap.class,NavigableMap 的实现类,返回最接近的一个元素
ConcurrentSkipListMap.class,它也是 NavigableMap 的实现类(要求元素之间可以比较),同时它比 ConcurrentHashMap 更加 scalable——ConcurrentHashMap 并不保证它的操作时间,并且你可以自己来调整它的 load factor;但是 ConcurrentSkipListMap 可以保证 O(log n) 的性能,同时不能自己来调整它的并发参数,只有你确实需要快速的遍历操作,并且可以承受额外的插入开销的时候,才去使用它
ConcurrentSkipListSet.class,和上面类似,只不过 map 变成了 set
CopyOnWriteArrayList.class,copy-on-write 模式的 array list,每当需要插入元素,不在原 list 上操作,而是会新建立一个 list,适合读远远大于写并且写时间并苛刻的场景
CopyOnWriteArraySet.class,和上面类似,list 变成 set 而已
public static void main(String[] args) {
TestThread mt = new TestThread();
mt.setName("T_T");
mt.start();
try {
Thread.currentThread().sleep(10);
mt.park();
Thread.currentThread().sleep(20);
mt.unPark();
Thread.currentThread().sleep(30);
mt.park();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static class TestThread extends Thread {
private boolean isPark = false;
@Override
public void run() {
System.out.println(" Enter Thread running.....");
while (true) {
if (isPark) {
System.out.println("Thread is Park.....");
LockSupport.park();
}
}
}
public void park() {
isPark = true;
}
public void unPark() {
isPark = false;
LockSupport.unpark(this);
System.out.println("Thread is unpark.....");
}
}
线程使用sheep
public static void main(String[] args)throws InterruptedException{
int i=0;
boolean iswhile=true;
while (iswhile) {
Integer integers = (int) (100 * Math.random());
String ss = SendGET("https://blog.youkuaiyun.com/iteye_9328/article/details/102821001", String.valueOf(integers));
System.out.println(ss);
synchronized(new Object()){ //自行处理
i = i + 1;
Thread.sleep(60000);
if (i == 300) {
iswhile = false;
}
}
}
}
public static String SendGET(String url,String param){
System.out.println("访问的url:"+url+"?"+param);
String result="";//访问返回结果
BufferedReader read=null;//读取访问结果
try {
//创建url
URL realurl=new URL(url+"?"+param);
//打开连接
URLConnection connection=realurl.openConnection();
// 设置通用的请求属性
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
//建立连接
connection.connect();
// 获取所有响应头字段
Map<String, List<String>> map = connection.getHeaderFields();
// 遍历所有的响应头字段,获取到cookies等
for (String key : map.keySet()) {
System.out.println(key + "--->" + map.get(key));
}
// 定义 BufferedReader输入流来读取URL的响应
read = new BufferedReader(new InputStreamReader(
connection.getInputStream(),"UTF-8"));
String line;//循环读取
while ((line = read.readLine()) != null) {
result += line;
}
} catch (IOException e) {
e.printStackTrace();
}finally{
if(read!=null){//关闭流
try {
read.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return result;
}